summaryrefslogtreecommitdiff
path: root/game/client/tf/vgui
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/tf/vgui')
-rw-r--r--game/client/tf/vgui/ObjectControlPanel.cpp183
-rw-r--r--game/client/tf/vgui/ObjectControlPanel.h108
-rw-r--r--game/client/tf/vgui/backgroundpanel.cpp677
-rw-r--r--game/client/tf/vgui/backgroundpanel.h53
-rw-r--r--game/client/tf/vgui/blueprint_panel.cpp185
-rw-r--r--game/client/tf/vgui/blueprint_panel.h61
-rw-r--r--game/client/tf/vgui/character_info_panel.cpp864
-rw-r--r--game/client/tf/vgui/character_info_panel.h135
-rw-r--r--game/client/tf/vgui/charinfo_armory_subpanel.cpp945
-rw-r--r--game/client/tf/vgui/charinfo_armory_subpanel.h151
-rw-r--r--game/client/tf/vgui/charinfo_loadout_subpanel.cpp1244
-rw-r--r--game/client/tf/vgui/charinfo_loadout_subpanel.h177
-rw-r--r--game/client/tf/vgui/class_loadout_panel.cpp1539
-rw-r--r--game/client/tf/vgui/class_loadout_panel.h157
-rw-r--r--game/client/tf/vgui/collection_crafting_panel.cpp829
-rw-r--r--game/client/tf/vgui/collection_crafting_panel.h225
-rw-r--r--game/client/tf/vgui/crafting_panel.cpp1738
-rw-r--r--game/client/tf/vgui/crafting_panel.h202
-rw-r--r--game/client/tf/vgui/crate_detail_panels.cpp388
-rw-r--r--game/client/tf/vgui/crate_detail_panels.h62
-rw-r--r--game/client/tf/vgui/drawing_panel.cpp335
-rw-r--r--game/client/tf/vgui/drawing_panel.h84
-rw-r--r--game/client/tf/vgui/dynamic_recipe_subpanel.cpp2013
-rw-r--r--game/client/tf/vgui/dynamic_recipe_subpanel.h267
-rw-r--r--game/client/tf/vgui/halloween_offering_panel.cpp88
-rw-r--r--game/client/tf/vgui/halloween_offering_panel.h54
-rw-r--r--game/client/tf/vgui/item_ad_panel.cpp489
-rw-r--r--game/client/tf/vgui/item_ad_panel.h113
-rw-r--r--game/client/tf/vgui/item_quickswitch.cpp792
-rw-r--r--game/client/tf/vgui/item_quickswitch.h72
-rw-r--r--game/client/tf/vgui/item_slot_panel.cpp301
-rw-r--r--game/client/tf/vgui/item_slot_panel.h54
-rw-r--r--game/client/tf/vgui/loadout_preset_panel.cpp250
-rw-r--r--game/client/tf/vgui/loadout_preset_panel.h64
-rw-r--r--game/client/tf/vgui/modelimagepanel.cpp214
-rw-r--r--game/client/tf/vgui/modelimagepanel.h43
-rw-r--r--game/client/tf/vgui/quest_item_panel.cpp1625
-rw-r--r--game/client/tf/vgui/quest_item_panel.h265
-rw-r--r--game/client/tf/vgui/quest_log_panel.cpp1182
-rw-r--r--game/client/tf/vgui/quest_log_panel.h132
-rw-r--r--game/client/tf/vgui/quest_notification_panel.cpp533
-rw-r--r--game/client/tf/vgui/quest_notification_panel.h180
-rw-r--r--game/client/tf/vgui/report_player_dialog.cpp305
-rw-r--r--game/client/tf/vgui/report_player_dialog.h56
-rw-r--r--game/client/tf/vgui/sc_hinticon.cpp70
-rw-r--r--game/client/tf/vgui/sc_hinticon.h39
-rw-r--r--game/client/tf/vgui/select_player_dialog.cpp446
-rw-r--r--game/client/tf/vgui/select_player_dialog.h107
-rw-r--r--game/client/tf/vgui/softline.cpp96
-rw-r--r--game/client/tf/vgui/softline.h35
-rw-r--r--game/client/tf/vgui/store/tf_store.cpp7
-rw-r--r--game/client/tf/vgui/store/tf_store.h12
-rw-r--r--game/client/tf/vgui/store/tf_store_page_base.cpp278
-rw-r--r--game/client/tf/vgui/store/tf_store_page_base.h121
-rw-r--r--game/client/tf/vgui/store/tf_store_panel_base.cpp133
-rw-r--r--game/client/tf/vgui/store/tf_store_panel_base.h54
-rw-r--r--game/client/tf/vgui/store/tf_store_preview_item_base.cpp1183
-rw-r--r--game/client/tf/vgui/store/tf_store_preview_item_base.h102
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_page.cpp96
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_page.h39
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_page_maps.cpp30
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_page_maps.h33
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_panel.cpp69
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_panel.h38
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_preview_item.cpp100
-rw-r--r--game/client/tf/vgui/store/v1/tf_store_preview_item.h41
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.cpp83
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.h36
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_page2.cpp849
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_page2.h82
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_page_maps2.cpp59
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_page_maps2.h35
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_panel2.cpp109
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_panel2.h41
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_preview_item2.cpp1367
-rw-r--r--game/client/tf/vgui/store/v2/tf_store_preview_item2.h177
-rw-r--r--game/client/tf/vgui/strange_count_transfer_panel.cpp269
-rw-r--r--game/client/tf/vgui/strange_count_transfer_panel.h60
-rw-r--r--game/client/tf/vgui/testitem_dialog.cpp754
-rw-r--r--game/client/tf/vgui/testitem_dialog.h99
-rw-r--r--game/client/tf/vgui/testitem_root.cpp931
-rw-r--r--game/client/tf/vgui/testitem_root.h109
-rw-r--r--game/client/tf/vgui/tf_arenateammenu.cpp426
-rw-r--r--game/client/tf/vgui/tf_arenateammenu.h79
-rw-r--r--game/client/tf/vgui/tf_asyncpanel.cpp121
-rw-r--r--game/client/tf/vgui/tf_asyncpanel.h40
-rw-r--r--game/client/tf/vgui/tf_badge_panel.cpp49
-rw-r--r--game/client/tf/vgui/tf_badge_panel.h32
-rw-r--r--game/client/tf/vgui/tf_classmenu.cpp1703
-rw-r--r--game/client/tf/vgui/tf_classmenu.h177
-rw-r--r--game/client/tf/vgui/tf_clientscoreboard.cpp2424
-rw-r--r--game/client/tf/vgui/tf_clientscoreboard.h183
-rw-r--r--game/client/tf/vgui/tf_controls.cpp1292
-rw-r--r--game/client/tf/vgui/tf_controls.h308
-rw-r--r--game/client/tf/vgui/tf_giveawayitempanel.cpp447
-rw-r--r--game/client/tf/vgui/tf_giveawayitempanel.h108
-rw-r--r--game/client/tf/vgui/tf_imagepanel.cpp87
-rw-r--r--game/client/tf/vgui/tf_imagepanel.h41
-rw-r--r--game/client/tf/vgui/tf_intromenu.cpp703
-rw-r--r--game/client/tf/vgui/tf_intromenu.h128
-rw-r--r--game/client/tf/vgui/tf_item_card_panel.cpp767
-rw-r--r--game/client/tf/vgui/tf_item_card_panel.h159
-rw-r--r--game/client/tf/vgui/tf_item_inspection_panel.cpp391
-rw-r--r--game/client/tf/vgui/tf_item_inspection_panel.h54
-rw-r--r--game/client/tf/vgui/tf_item_pickup_panel.cpp384
-rw-r--r--game/client/tf/vgui/tf_item_pickup_panel.h58
-rw-r--r--game/client/tf/vgui/tf_layeredmappanel.cpp343
-rw-r--r--game/client/tf/vgui/tf_layeredmappanel.h106
-rw-r--r--game/client/tf/vgui/tf_leaderboardpanel.cpp103
-rw-r--r--game/client/tf/vgui/tf_leaderboardpanel.h36
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame.cpp630
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame.h88
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame_casual.cpp268
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame_casual.h82
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame_comp.cpp297
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame_comp.h77
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame_mvm.cpp357
-rw-r--r--game/client/tf/vgui/tf_lobby_container_frame_mvm.h49
-rw-r--r--game/client/tf/vgui/tf_lobbypanel.cpp1107
-rw-r--r--game/client/tf/vgui/tf_lobbypanel.h238
-rw-r--r--game/client/tf/vgui/tf_lobbypanel_casual.cpp621
-rw-r--r--game/client/tf/vgui/tf_lobbypanel_casual.h77
-rw-r--r--game/client/tf/vgui/tf_lobbypanel_comp.cpp826
-rw-r--r--game/client/tf/vgui/tf_lobbypanel_comp.h139
-rw-r--r--game/client/tf/vgui/tf_lobbypanel_mvm.cpp1114
-rw-r--r--game/client/tf/vgui/tf_lobbypanel_mvm.h151
-rw-r--r--game/client/tf/vgui/tf_mapinfomenu.cpp648
-rw-r--r--game/client/tf/vgui/tf_mapinfomenu.h85
-rw-r--r--game/client/tf/vgui/tf_match_join_handlers.cpp145
-rw-r--r--game/client/tf/vgui/tf_match_join_handlers.h58
-rw-r--r--game/client/tf/vgui/tf_match_summary.cpp1481
-rw-r--r--game/client/tf/vgui/tf_match_summary.h248
-rw-r--r--game/client/tf/vgui/tf_matchmaking_dashboard.cpp594
-rw-r--r--game/client/tf/vgui/tf_matchmaking_dashboard.h158
-rw-r--r--game/client/tf/vgui/tf_matchmaking_dashboard_new_match_found.cpp99
-rw-r--r--game/client/tf/vgui/tf_matchmaking_dashboard_next_map_voting.cpp293
-rw-r--r--game/client/tf/vgui/tf_matchmaking_dashboard_next_map_winner.cpp94
-rw-r--r--game/client/tf/vgui/tf_matchmaking_panel.cpp580
-rw-r--r--game/client/tf/vgui/tf_matchmaking_panel.h75
-rw-r--r--game/client/tf/vgui/tf_mouseforwardingpanel.cpp81
-rw-r--r--game/client/tf/vgui/tf_mouseforwardingpanel.h34
-rw-r--r--game/client/tf/vgui/tf_overview.cpp859
-rw-r--r--game/client/tf/vgui/tf_overview.h80
-rw-r--r--game/client/tf/vgui/tf_particlepanel.cpp556
-rw-r--r--game/client/tf/vgui/tf_particlepanel.h84
-rw-r--r--game/client/tf/vgui/tf_ping_panel.cpp286
-rw-r--r--game/client/tf/vgui/tf_ping_panel.h58
-rw-r--r--game/client/tf/vgui/tf_playermodelpanel.cpp2893
-rw-r--r--game/client/tf/vgui/tf_playermodelpanel.h222
-rw-r--r--game/client/tf/vgui/tf_playerpanel.cpp411
-rw-r--r--game/client/tf/vgui/tf_playerpanel.h78
-rw-r--r--game/client/tf/vgui/tf_pvp_rank_panel.cpp762
-rw-r--r--game/client/tf/vgui/tf_pvp_rank_panel.h114
-rw-r--r--game/client/tf/vgui/tf_roundinfo.cpp623
-rw-r--r--game/client/tf/vgui/tf_roundinfo.h73
-rw-r--r--game/client/tf/vgui/tf_spectatorgui.cpp1279
-rw-r--r--game/client/tf/vgui/tf_spectatorgui.h140
-rw-r--r--game/client/tf/vgui/tf_statsummary.cpp1506
-rw-r--r--game/client/tf/vgui/tf_statsummary.h142
-rw-r--r--game/client/tf/vgui/tf_teammenu.cpp901
-rw-r--r--game/client/tf/vgui/tf_teammenu.h136
-rw-r--r--game/client/tf/vgui/tf_textwindow.cpp274
-rw-r--r--game/client/tf/vgui/tf_textwindow.h62
-rw-r--r--game/client/tf/vgui/tf_training_ui.cpp2205
-rw-r--r--game/client/tf/vgui/tf_vgui_video.cpp112
-rw-r--r--game/client/tf/vgui/tf_vgui_video.h42
-rw-r--r--game/client/tf/vgui/tf_viewport.cpp482
-rw-r--r--game/client/tf/vgui/tf_viewport.h54
-rw-r--r--game/client/tf/vgui/tf_warinfopanel.cpp532
-rw-r--r--game/client/tf/vgui/tf_warinfopanel.h100
-rw-r--r--game/client/tf/vgui/vgui_critpanel.cpp255
-rw-r--r--game/client/tf/vgui/vgui_pda_panel.cpp336
-rw-r--r--game/client/tf/vgui/vgui_rootpanel_tf.cpp160
-rw-r--r--game/client/tf/vgui/vgui_rootpanel_tf.h56
-rw-r--r--game/client/tf/vgui/vgui_rotation_slider.cpp74
-rw-r--r--game/client/tf/vgui/vgui_rotation_slider.h43
176 files changed, 65156 insertions, 0 deletions
diff --git a/game/client/tf/vgui/ObjectControlPanel.cpp b/game/client/tf/vgui/ObjectControlPanel.cpp
new file mode 100644
index 0000000..da24fd2
--- /dev/null
+++ b/game/client/tf/vgui/ObjectControlPanel.cpp
@@ -0,0 +1,183 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "ObjectControlPanel.h"
+#include <vgui_controls/Controls.h>
+#include <vgui_controls/Label.h>
+#include "vgui_bitmapbutton.h"
+#include <vgui/ISurface.h>
+#include <vgui/IVGui.h>
+#include "c_tf_player.h"
+#include "clientmode_tf.h"
+#include <vgui/IScheme.h>
+#include <vgui_controls/Slider.h>
+#include "vgui_rotation_slider.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+#define DISMANTLE_WAIT_TIME 5.0
+
+
+//-----------------------------------------------------------------------------
+// Standard VGUI panel for objects
+//-----------------------------------------------------------------------------
+DECLARE_VGUI_SCREEN_FACTORY( CObjectControlPanel, "object_control_panel" );
+
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CObjectControlPanel::CObjectControlPanel( vgui::Panel *parent, const char *panelName )
+ : BaseClass( parent, panelName, NULL )
+{
+ // Make some high-level panels to group stuff we want to activate/deactivate
+ m_pActivePanel = new CCommandChainingPanel( this, "ActivePanel" );
+
+ SetCursor( vgui::dc_none ); // don't draw a VGUI cursor for this panel, and for its children
+
+ // Make sure these are behind everything
+ m_pActivePanel->SetZPos( -1 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Initialization
+//-----------------------------------------------------------------------------
+bool CObjectControlPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData )
+{
+ // Make sure we get ticked...
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ if (!BaseClass::Init(pKeyValues, pInitData))
+ return false;
+
+ SetCursor( vgui::dc_none ); // don't draw a VGUI cursor for this panel, and for its children
+
+ // Make the bounds of the sub-panels match
+ int x, y, w, h;
+ GetBounds( x, y, w, h );
+ m_pActivePanel->SetBounds( x, y, w, h );
+
+ // Make em all invisible
+ m_pActivePanel->SetVisible( false );
+ m_pCurrentPanel = m_pActivePanel;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the object it's attached to
+//-----------------------------------------------------------------------------
+C_BaseObject *CObjectControlPanel::GetOwningObject() const
+{
+ C_BaseEntity *pScreenEnt = GetEntity();
+ if (!pScreenEnt)
+ return NULL;
+
+ C_BaseEntity *pObj = pScreenEnt->GetOwnerEntity();
+ if (!pObj)
+ return NULL;
+
+ Assert( dynamic_cast<C_BaseObject*>(pObj) );
+ return static_cast<C_BaseObject*>(pObj);
+}
+
+
+//-----------------------------------------------------------------------------
+// Ticks the panel when its in its various states
+//-----------------------------------------------------------------------------
+void CObjectControlPanel::OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer )
+{
+ //ShowDismantleButton( !(pObj->GetFlags() & OF_CANNOT_BE_DISMANTLED) && pObj->GetOwner() == pLocalPlayer );
+}
+
+vgui::Panel* CObjectControlPanel::TickCurrentPanel()
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ C_BaseObject *pObj = GetOwningObject();
+
+ m_pCurrentPanel = GetActivePanel();
+ OnTickActive(pObj, pLocalPlayer);
+
+ return m_pCurrentPanel;
+}
+
+void CObjectControlPanel::SendToServerObject( const char *pMsg )
+{
+ C_BaseObject *pObj = GetOwningObject();
+ if (pObj)
+ {
+ pObj->SendClientCommand( pMsg );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Frame-based update
+//-----------------------------------------------------------------------------
+void CObjectControlPanel::OnTick()
+{
+ BaseClass::OnTick();
+
+ C_BaseObject *pObj = GetOwningObject();
+ if (!pObj)
+ return;
+
+ if ( IsVisible() )
+ {
+ // Update the current subpanel
+ m_pCurrentPanel->SetVisible( false );
+
+ m_pCurrentPanel = TickCurrentPanel();
+
+ m_pCurrentPanel->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Button click handlers
+//-----------------------------------------------------------------------------
+void CObjectControlPanel::OnCommand( const char *command )
+{
+ BaseClass::OnCommand(command);
+}
+
+DECLARE_VGUI_SCREEN_FACTORY( CRotatingObjectControlPanel, "rotating_object_control_panel" );
+
+
+//-----------------------------------------------------------------------------
+// This is a panel for an object that has rotational controls
+//-----------------------------------------------------------------------------
+CRotatingObjectControlPanel::CRotatingObjectControlPanel( vgui::Panel *parent, const char *panelName )
+ : BaseClass( parent, panelName )
+{
+}
+
+bool CRotatingObjectControlPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData )
+{
+ // Grab ahold of certain well-known controls
+ m_pRotationSlider = new CRotationSlider( GetActivePanel(), "RotationSlider" );
+ m_pRotationLabel = new vgui::Label( GetActivePanel(), "RotationLabel", "Rotation Control" );
+
+ if (!BaseClass::Init(pKeyValues, pInitData))
+ return false;
+
+ m_pRotationSlider->SetControlledObject( GetOwningObject() );
+
+ return true;
+}
+
+void CRotatingObjectControlPanel::OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer )
+{
+ BaseClass::OnTickActive( pObj, pLocalPlayer );
+ bool bEnable = (pObj->GetOwner() == pLocalPlayer);
+ m_pRotationSlider->SetVisible( bEnable );
+ m_pRotationLabel->SetVisible( bEnable );
+}
+
diff --git a/game/client/tf/vgui/ObjectControlPanel.h b/game/client/tf/vgui/ObjectControlPanel.h
new file mode 100644
index 0000000..9db962a
--- /dev/null
+++ b/game/client/tf/vgui/ObjectControlPanel.h
@@ -0,0 +1,108 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Clients CBaseObject
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef OBJECTCONTROLPANEL_H
+#define OBJECTCONTROLPANEL_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "c_vguiscreen.h"
+
+namespace vgui
+{
+ class Panel;
+ class Label;
+ class Button;
+}
+
+class C_BaseObject;
+class CRotationSlider;
+class C_TFPlayer;
+
+//-----------------------------------------------------------------------------
+// Base class for all vgui screens on objects:
+//-----------------------------------------------------------------------------
+class CObjectControlPanel : public CVGuiScreenPanel
+{
+ DECLARE_CLASS( CObjectControlPanel, CVGuiScreenPanel );
+
+public:
+ CObjectControlPanel( vgui::Panel *parent, const char *panelName );
+
+ virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData );
+ virtual void OnCommand( const char *command );
+ virtual void OnTick();
+
+protected:
+ // Method to add controls to particular panels
+ vgui::Panel *GetActivePanel() { return m_pActivePanel; }
+
+ // Override these to deal with various controls in various modes
+ virtual void OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer );
+
+ C_BaseObject *GetOwningObject() const;
+
+ // This should update the current panel and return that panel.
+ virtual vgui::Panel* TickCurrentPanel();
+
+ // Send a message to the owner.
+ void SendToServerObject( const char *pMsg );
+
+private:
+
+ vgui::EditablePanel *m_pActivePanel;
+
+ vgui::Panel *m_pCurrentPanel;
+};
+
+
+// This is used for child panels. It forwards the messages to the parent panel.
+class CCommandChainingPanel : public vgui::EditablePanel
+{
+ typedef vgui::EditablePanel BaseClass;
+
+public:
+ CCommandChainingPanel( vgui::Panel *parent, const char *panelName ) :
+ BaseClass( parent, panelName )
+ {
+ SetPaintBackgroundEnabled( false );
+ }
+
+ void OnCommand( const char *command )
+ {
+ BaseClass::OnCommand( command );
+ if (GetParent())
+ {
+ GetParent()->OnCommand(command);
+ }
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// This is a panel for an object that has rotational controls
+//-----------------------------------------------------------------------------
+class CRotatingObjectControlPanel : public CObjectControlPanel
+{
+ DECLARE_CLASS( CRotatingObjectControlPanel, CObjectControlPanel );
+
+public:
+ CRotatingObjectControlPanel( vgui::Panel *parent, const char *panelName );
+
+ virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData );
+
+protected:
+ virtual void OnTickActive( C_BaseObject *pObj, C_TFPlayer *pLocalPlayer );
+
+private:
+ CRotationSlider *m_pRotationSlider;
+ vgui::Label *m_pRotationLabel;
+};
+
+#endif // OBJECTCONTROLPANEL_H
diff --git a/game/client/tf/vgui/backgroundpanel.cpp b/game/client/tf/vgui/backgroundpanel.cpp
new file mode 100644
index 0000000..9783c21
--- /dev/null
+++ b/game/client/tf/vgui/backgroundpanel.cpp
@@ -0,0 +1,677 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "backgroundpanel.h"
+
+#include <vgui/IVGui.h>
+#include <vgui/IScheme.h>
+#include <vgui/ISurface.h>
+#include <vgui_controls/Label.h>
+#include <vgui/ILocalize.h>
+#include "vgui_controls/BuildGroup.h"
+#include "vgui_controls/BitmapImagePanel.h"
+
+using namespace vgui;
+
+#define DEBUG_WINDOW_RESIZING 0
+#define DEBUG_WINDOW_REPOSITIONING 0
+
+//-----------------------------------------------------------------------------
+const int NumSegments = 7;
+static int coord[NumSegments+1] = {
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 6,
+ 9,
+ 10
+};
+
+//-----------------------------------------------------------------------------
+void DrawRoundedBackground( Color bgColor, int wide, int tall )
+{
+ int x1, x2, y1, y2;
+ surface()->DrawSetColor(bgColor);
+ surface()->DrawSetTextColor(bgColor);
+
+ int i;
+
+ // top-left corner --------------------------------------------------------
+ int xDir = 1;
+ int yDir = -1;
+ int xIndex = 0;
+ int yIndex = NumSegments - 1;
+ int xMult = 1;
+ int yMult = 1;
+ int x = 0;
+ int y = 0;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ y2 = y + coord[NumSegments];
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // top-right corner -------------------------------------------------------
+ xDir = 1;
+ yDir = -1;
+ xIndex = 0;
+ yIndex = NumSegments - 1;
+ x = wide;
+ y = 0;
+ xMult = -1;
+ yMult = 1;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ y2 = y + coord[NumSegments];
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // bottom-right corner ----------------------------------------------------
+ xDir = 1;
+ yDir = -1;
+ xIndex = 0;
+ yIndex = NumSegments - 1;
+ x = wide;
+ y = tall;
+ xMult = -1;
+ yMult = -1;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = y - coord[NumSegments];
+ y2 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // bottom-left corner -----------------------------------------------------
+ xDir = 1;
+ yDir = -1;
+ xIndex = 0;
+ yIndex = NumSegments - 1;
+ x = 0;
+ y = tall;
+ xMult = 1;
+ yMult = -1;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = y - coord[NumSegments];
+ y2 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // paint between top left and bottom left ---------------------------------
+ x1 = 0;
+ x2 = coord[NumSegments];
+ y1 = coord[NumSegments];
+ y2 = tall - coord[NumSegments];
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+
+ // paint between left and right -------------------------------------------
+ x1 = coord[NumSegments];
+ x2 = wide - coord[NumSegments];
+ y1 = 0;
+ y2 = tall;
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+
+ // paint between top right and bottom right -------------------------------
+ x1 = wide - coord[NumSegments];
+ x2 = wide;
+ y1 = coord[NumSegments];
+ y2 = tall - coord[NumSegments];
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+}
+
+//-----------------------------------------------------------------------------
+void DrawRoundedBorder( Color borderColor, int wide, int tall )
+{
+ int x1, x2, y1, y2;
+ surface()->DrawSetColor(borderColor);
+ surface()->DrawSetTextColor(borderColor);
+
+ int i;
+
+ // top-left corner --------------------------------------------------------
+ int xDir = 1;
+ int yDir = -1;
+ int xIndex = 0;
+ int yIndex = NumSegments - 1;
+ int xMult = 1;
+ int yMult = 1;
+ int x = 0;
+ int y = 0;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // top-right corner -------------------------------------------------------
+ xDir = 1;
+ yDir = -1;
+ xIndex = 0;
+ yIndex = NumSegments - 1;
+ x = wide;
+ y = 0;
+ xMult = -1;
+ yMult = 1;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // bottom-right corner ----------------------------------------------------
+ xDir = 1;
+ yDir = -1;
+ xIndex = 0;
+ yIndex = NumSegments - 1;
+ x = wide;
+ y = tall;
+ xMult = -1;
+ yMult = -1;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // bottom-left corner -----------------------------------------------------
+ xDir = 1;
+ yDir = -1;
+ xIndex = 0;
+ yIndex = NumSegments - 1;
+ x = 0;
+ y = tall;
+ xMult = 1;
+ yMult = -1;
+ for ( i=0; i<NumSegments; ++i )
+ {
+ x1 = MIN( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ x2 = MAX( x + coord[xIndex]*xMult, x + coord[xIndex+1]*xMult );
+ y1 = MIN( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ y2 = MAX( y + coord[yIndex]*yMult, y + coord[yIndex+1]*yMult );
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+ xIndex += xDir;
+ yIndex += yDir;
+ }
+
+ // top --------------------------------------------------------------------
+ x1 = coord[NumSegments];
+ x2 = wide - coord[NumSegments];
+ y1 = 0;
+ y2 = 1;
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+
+ // bottom -----------------------------------------------------------------
+ x1 = coord[NumSegments];
+ x2 = wide - coord[NumSegments];
+ y1 = tall - 1;
+ y2 = tall;
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+
+ // left -------------------------------------------------------------------
+ x1 = 0;
+ x2 = 1;
+ y1 = coord[NumSegments];
+ y2 = tall - coord[NumSegments];
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+
+ // right ------------------------------------------------------------------
+ x1 = wide - 1;
+ x2 = wide;
+ y1 = coord[NumSegments];
+ y2 = tall - coord[NumSegments];
+ surface()->DrawFilledRect( x1, y1, x2, y2 );
+}
+
+//-----------------------------------------------------------------------------
+class CaptionLabel : public Label
+{
+public:
+ CaptionLabel(Panel *parent, const char *panelName, const char *text) : Label(parent, panelName, text)
+ {
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ Label::ApplySchemeSettings( pScheme );
+ SetFont( pScheme->GetFont( "MenuTitle", IsProportional() ) );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: transform a normalized value into one that is scaled based the minimum
+// of the horizontal and vertical ratios
+//-----------------------------------------------------------------------------
+static int GetAlternateProportionalValueFromNormal(int normalizedValue)
+{
+ int wide, tall;
+ GetHudSize( wide, tall );
+ int proH, proW;
+ surface()->GetProportionalBase( proW, proH );
+ double scaleH = (double)tall / (double)proH;
+ double scaleW = (double)wide / (double)proW;
+ double scale = (scaleW < scaleH) ? scaleW : scaleH;
+
+ return (int)( normalizedValue * scale );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: transform a standard scaled value into one that is scaled based the minimum
+// of the horizontal and vertical ratios
+//-----------------------------------------------------------------------------
+int GetAlternateProportionalValueFromScaled(vgui::HScheme hScheme, int scaledValue)
+{
+ return GetAlternateProportionalValueFromNormal( scheme()->GetProportionalNormalizedValueEx( hScheme, scaledValue ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: moves and resizes a single control
+//-----------------------------------------------------------------------------
+static void RepositionControl( Panel *pPanel )
+{
+ int x, y, w, h;
+ pPanel->GetBounds(x, y, w, h);
+
+#if DEBUG_WINDOW_RESIZING
+ int x1, y1, w1, h1;
+ pPanel->GetBounds(x1, y1, w1, h1);
+ int x2, y2, w2, h2;
+ x2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),x1 );
+ y2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),y1 );
+ w2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),w1 );
+ h2 = scheme()->GetProportionalNormalizedValueEx( pPanel->GetScheme(),h1 );
+#endif
+
+ x = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),x);
+ y = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),y);
+ w = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),w);
+ h = GetAlternateProportionalValueFromScaled(pPanel->GetScheme(),h);
+
+ pPanel->SetBounds(x, y, w, h);
+
+#if DEBUG_WINDOW_RESIZING
+ DevMsg( "Resizing '%s' from (%d,%d) %dx%d to (%d,%d) %dx%d -- initially was (%d,%d) %dx%d\n",
+ pPanel->GetName(), x1, y1, w1, h1, x, y, w, h, x2, y2, w2, h2 );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets colors etc for background image panels
+//-----------------------------------------------------------------------------
+void ApplyBackgroundSchemeSettings( EditablePanel *pWindow, vgui::IScheme *pScheme )
+{
+ Color bgColor = Color( 255, 255, 255, pScheme->GetColor( "BgColor", Color( 0, 0, 0, 0 ) )[3] );
+ Color fgColor = pScheme->GetColor( "FgColor", Color( 0, 0, 0, 0 ) );
+
+ if ( !pWindow )
+ return;
+
+ CBitmapImagePanel *pBitmapPanel;
+
+ // corners --------------------------------------------
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "TopLeftPanel" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "TopRightPanel" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "BottomLeftPanel" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "BottomRightPanel" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+
+ // background -----------------------------------------
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "TopSolid" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "UpperMiddleSolid" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "LowerMiddleSolid" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+ pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "BottomSolid" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( bgColor );
+ }
+
+ // Logo -----------------------------------------------
+/* pBitmapPanel = dynamic_cast< CBitmapImagePanel * >(pWindow->FindChildByName( "ExclamationPanel" ));
+ if ( pBitmapPanel )
+ {
+ pBitmapPanel->setImageColor( fgColor );
+ }
+*/
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Re-aligns background image panels so they are touching.
+//-----------------------------------------------------------------------------
+static void FixupBackgroundPanels( EditablePanel *pWindow, int offsetX, int offsetY )
+{
+ if ( !pWindow )
+ return;
+
+ int screenWide, screenTall;
+ pWindow->GetSize( screenWide, screenTall );
+
+ int inset = GetAlternateProportionalValueFromNormal( 20 );
+ int cornerSize = GetAlternateProportionalValueFromNormal( 10 );
+
+ int titleHeight = GetAlternateProportionalValueFromNormal( 42 );
+ int mainHeight = GetAlternateProportionalValueFromNormal( 376 );
+
+ int logoSize = titleHeight;
+
+ int captionInset = GetAlternateProportionalValueFromNormal( 76 );
+
+ Panel *pPanel;
+
+ // corners --------------------------------------------
+ pPanel = pWindow->FindChildByName( "TopLeftPanel" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( offsetX + inset, offsetY + inset, cornerSize, cornerSize );
+ }
+
+ pPanel = pWindow->FindChildByName( "TopRightPanel" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( screenWide - offsetX - inset - cornerSize, offsetY + inset, cornerSize, cornerSize );
+ }
+
+ pPanel = pWindow->FindChildByName( "BottomLeftPanel" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( offsetX + inset, screenTall - offsetY - inset - cornerSize, cornerSize, cornerSize );
+ }
+
+ pPanel = pWindow->FindChildByName( "BottomRightPanel" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( screenWide - offsetX - inset - cornerSize, screenTall - offsetY - inset - cornerSize, cornerSize, cornerSize );
+ }
+
+ // background -----------------------------------------
+ pPanel = pWindow->FindChildByName( "TopSolid" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( offsetX + inset + cornerSize, offsetY + inset, screenWide - 2*offsetX - 2*inset - 2*cornerSize, cornerSize );
+ }
+
+ pPanel = pWindow->FindChildByName( "UpperMiddleSolid" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( offsetX + inset, offsetY + inset + cornerSize, screenWide - 2*offsetX - 2*inset, titleHeight );
+ }
+
+ pPanel = pWindow->FindChildByName( "LowerMiddleSolid" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( offsetX + inset + cornerSize, screenTall - offsetY - inset - cornerSize, screenWide - 2*offsetX - 2*inset - 2*cornerSize, cornerSize );
+ }
+
+ pPanel = pWindow->FindChildByName( "BottomSolid" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( offsetX + inset, screenTall - offsetY - inset - cornerSize - mainHeight, screenWide - 2*offsetX - 2*inset, mainHeight );
+ }
+
+ // transparent border ---------------------------------
+ pPanel = pWindow->FindChildByName( "TopClear" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( 0, 0, screenWide, offsetY + inset );
+ }
+
+ pPanel = pWindow->FindChildByName( "BottomClear" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( 0, screenTall - offsetY - inset, screenWide, offsetY + inset );
+ }
+
+ pPanel = pWindow->FindChildByName( "LeftClear" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( 0, offsetY + inset, offsetX + inset, screenTall - 2*offsetY - 2*inset );
+ }
+
+ pPanel = pWindow->FindChildByName( "RightClear" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -20 );
+ pPanel->SetBounds( screenWide - offsetX - inset, offsetY + inset, offsetX + inset, screenTall - 2*offsetY - 2*inset );
+ }
+
+ // Logo -----------------------------------------------
+/* int logoInset = (cornerSize + titleHeight - logoSize)/2;
+ pPanel = pWindow->FindChildByName( "ExclamationPanel" );
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -19 ); // higher than the background
+ pPanel->SetBounds( offsetX + inset + logoInset, offsetY + inset + logoInset, logoSize, logoSize );
+ }
+*/
+ // Title caption --------------------------------------
+ pPanel = dynamic_cast< Label * >(pWindow->FindChildByName( "CaptionLabel" ));
+ if ( pPanel )
+ {
+ pPanel->SetZPos( -19 ); // higher than the background
+ pPanel->SetBounds( offsetX + captionInset/*inset + 2*logoInset + logoSize*/, offsetY + inset /*+ logoInset*/, screenWide, logoSize );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates background image panels
+//-----------------------------------------------------------------------------
+void CreateBackground( EditablePanel *pWindow )
+{
+ // corners --------------------------------------------
+ new CBitmapImagePanel( pWindow, "TopLeftPanel", "gfx/vgui/round_corner_nw" );
+ new CBitmapImagePanel( pWindow, "TopRightPanel", "gfx/vgui/round_corner_ne" );
+ new CBitmapImagePanel( pWindow, "BottomLeftPanel", "gfx/vgui/round_corner_sw" );
+ new CBitmapImagePanel( pWindow, "BottomRightPanel", "gfx/vgui/round_corner_se" );
+
+ // background -----------------------------------------
+ new CBitmapImagePanel( pWindow, "TopSolid", "gfx/vgui/solid_background" );
+ new CBitmapImagePanel( pWindow, "UpperMiddleSolid", "gfx/vgui/solid_background" );
+ new CBitmapImagePanel( pWindow, "LowerMiddleSolid", "gfx/vgui/solid_background" );
+ new CBitmapImagePanel( pWindow, "BottomSolid", "gfx/vgui/solid_background" );
+
+ // transparent border ---------------------------------
+ new CBitmapImagePanel( pWindow, "TopClear", "gfx/vgui/trans_background" );
+ new CBitmapImagePanel( pWindow, "BottomClear", "gfx/vgui/trans_background" );
+ new CBitmapImagePanel( pWindow, "LeftClear", "gfx/vgui/trans_background" );
+ new CBitmapImagePanel( pWindow, "RightClear", "gfx/vgui/trans_background" );
+
+ // Logo -----------------------------------------------
+// new CBitmapImagePanel( pWindow, "ExclamationPanel", "gfx/vgui/TF_logo" );
+
+ // Title caption --------------------------------------
+ Panel *pPanel = dynamic_cast< Label * >(pWindow->FindChildByName( "CaptionLabel" ));
+ if ( !pPanel )
+ new CaptionLabel( pWindow, "CaptionLabel", "" );
+}
+
+void ResizeWindowControls( EditablePanel *pWindow, int tall, int wide, int offsetX, int offsetY )
+{
+ if (!pWindow || !pWindow->GetBuildGroup() || !pWindow->GetBuildGroup()->GetPanelList())
+ return;
+
+ CUtlVector<PHandle> *panelList = pWindow->GetBuildGroup()->GetPanelList();
+ CUtlVector<Panel *> resizedPanels;
+ CUtlVector<Panel *> movedPanels;
+
+ // Resize to account for 1.25 aspect ratio (1280x1024) screens
+ {
+ for ( int i = 0; i < panelList->Size(); ++i )
+ {
+ PHandle handle = (*panelList)[i];
+
+ Panel *panel = handle.Get();
+
+ bool found = false;
+ for ( int j = 0; j < resizedPanels.Size(); ++j )
+ {
+ if (panel == resizedPanels[j])
+ found = true;
+ }
+
+ if (!panel || found)
+ {
+ continue;
+ }
+
+ resizedPanels.AddToTail( panel ); // don't move a panel more than once
+
+ if ( panel != pWindow )
+ {
+ RepositionControl( panel );
+ }
+ }
+ }
+
+ // and now re-center them. Woohoo!
+ for ( int i = 0; i < panelList->Size(); ++i )
+ {
+ PHandle handle = (*panelList)[i];
+
+ Panel *panel = handle.Get();
+
+ bool found = false;
+ for ( int j = 0; j < movedPanels.Size(); ++j )
+ {
+ if (panel == movedPanels[j])
+ found = true;
+ }
+
+ if (!panel || found)
+ {
+ continue;
+ }
+
+ movedPanels.AddToTail( panel ); // don't move a panel more than once
+
+ if ( panel != pWindow )
+ {
+ int x, y;
+
+ panel->GetPos( x, y );
+ panel->SetPos( x + offsetX, y + offsetY );
+
+#if DEBUG_WINDOW_REPOSITIONING
+ DevMsg( "Repositioning '%s' from (%d,%d) to (%d,%d) -- a distance of (%d,%d)\n",
+ panel->GetName(), x, y, x + offsetX, y + offsetY, offsetX, offsetY );
+#endif
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resizes windows to fit completely on-screen (for 1280x1024), and
+// centers them on the screen. Sub-controls are also resized and moved.
+//-----------------------------------------------------------------------------
+void LayoutBackgroundPanel( EditablePanel *pWindow )
+{
+ if ( !pWindow )
+ return;
+
+ int screenW, screenH;
+ GetHudSize( screenW, screenH );
+
+ int wide, tall;
+ pWindow->GetSize( wide, tall );
+
+ int offsetX = 0;
+ int offsetY = 0;
+
+ // Slide everything over to the center
+ pWindow->SetBounds( 0, 0, screenW, screenH );
+
+ if ( wide != screenW || tall != screenH )
+ {
+ wide = GetAlternateProportionalValueFromScaled(pWindow->GetScheme(), wide);
+ tall = GetAlternateProportionalValueFromScaled(pWindow->GetScheme(), tall);
+
+ offsetX = (screenW - wide)/2;
+ offsetY = (screenH - tall)/2;
+
+ ResizeWindowControls( pWindow, tall, wide, offsetX, offsetY );
+ }
+
+ // now that the panels are moved/resized, look for some bg panels, and re-align them
+ FixupBackgroundPanels( pWindow, offsetX, offsetY );
+}
+
+//-----------------------------------------------------------------------------
+
diff --git a/game/client/tf/vgui/backgroundpanel.h b/game/client/tf/vgui/backgroundpanel.h
new file mode 100644
index 0000000..18ebc23
--- /dev/null
+++ b/game/client/tf/vgui/backgroundpanel.h
@@ -0,0 +1,53 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TFBACKGROUND_H
+#define TFBACKGROUND_H
+
+#include <vgui_controls/Frame.h>
+#include <vgui_controls/EditablePanel.h>
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates background image panels
+//-----------------------------------------------------------------------------
+void CreateBackground( vgui::EditablePanel *pWindow );
+
+//-----------------------------------------------------------------------------
+// Purpose: Resizes windows to fit completely on-screen (for 1280x1024), and
+// centers them on the screen. Sub-controls are also resized and moved.
+//-----------------------------------------------------------------------------
+void LayoutBackgroundPanel( vgui::EditablePanel *pWindow );
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets colors etc for background image panels
+//-----------------------------------------------------------------------------
+void ApplyBackgroundSchemeSettings( vgui::EditablePanel *pWindow, vgui::IScheme *pScheme );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ResizeWindowControls( vgui::EditablePanel *pWindow, int tall, int wide, int offsetX, int offsetY );
+
+//-----------------------------------------------------------------------------
+// Purpose: transform a standard scaled value into one that is scaled based the minimum
+// of the horizontal and vertical ratios
+//-----------------------------------------------------------------------------
+int GetAlternateProportionalValueFromScaled( vgui::HScheme hScheme, int scaledValue );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void DrawRoundedBackground( Color bgColor, int wide, int tall );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void DrawRoundedBorder( Color borderColor, int wide, int tall );
+
+//-----------------------------------------------------------------------------
+
+#endif // TFBACKGROUND_H
diff --git a/game/client/tf/vgui/blueprint_panel.cpp b/game/client/tf/vgui/blueprint_panel.cpp
new file mode 100644
index 0000000..c0bc484
--- /dev/null
+++ b/game/client/tf/vgui/blueprint_panel.cpp
@@ -0,0 +1,185 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "vgui/IInput.h"
+#include <vgui/IVGui.h>
+#include <vgui/IScheme.h>
+#include "blueprint_panel.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/Label.h"
+#include "vgui_controls/Button.h"
+#include "ienginevgui.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "renderparm.h"
+
+DECLARE_BUILD_FACTORY( CBlueprintPanel );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBlueprintPanel::CBlueprintPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name )
+{
+ m_bClickable = false;
+ m_bMouseOver = false;
+
+ m_bInStack = false;
+
+ SetActAsButton( false, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/build_menu/base_selectable.res" );
+
+ m_pItemNameLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemNameLabel") );
+ m_pItemCostLabel = dynamic_cast<vgui::Label*>( FindChildByName("CostLabel") );
+ m_pIcon = dynamic_cast<CIconPanel*>( FindChildByName("BuildingIcon") );
+ m_pMetalIcon = dynamic_cast<CIconPanel*>( FindChildByName("MetalIcon") );
+ m_pBackground = dynamic_cast<CIconPanel*>( FindChildByName("ItemBackground") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::SetObjectInfo( const CObjectInfo* pNewInfo )
+{
+ m_pObjectInfo = pNewInfo;
+
+ bool bVisible = pNewInfo != NULL;
+
+ if ( m_pItemNameLabel )
+ {
+ if ( m_pObjectInfo )
+ {
+ m_pItemNameLabel->SetText( m_pObjectInfo->m_pBuilderWeaponName );
+ }
+ m_pItemNameLabel->SetVisible( bVisible );
+ }
+
+ if ( m_pItemCostLabel )
+ {
+ if ( m_pObjectInfo )
+ {
+ V_snprintf( m_pszCost, sizeof( m_pszCost ), "%i", m_pObjectInfo->m_Cost );
+ m_pItemCostLabel->SetText( m_pszCost );
+ }
+ m_pItemCostLabel->SetVisible( bVisible );
+ }
+
+ if ( m_pIcon )
+ {
+ if ( m_pObjectInfo )
+ {
+ m_pIcon->SetIcon( m_pObjectInfo->m_pIconMenu );
+ }
+ m_pIcon->SetVisible( bVisible );
+ }
+
+ if ( m_pMetalIcon )
+ {
+ m_pMetalIcon->SetVisible( bVisible );
+ }
+
+ if ( m_pBackground )
+ {
+ m_pBackground->SetVisible( bVisible );
+ }
+
+ SetVisible( bVisible );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::SetActAsButton( bool bClickable, bool bMouseOver )
+{
+ m_bClickable = bClickable;
+ m_bMouseOver = bMouseOver;
+
+ SetMouseInputEnabled( m_bClickable || m_bMouseOver );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::OnCursorEntered( void )
+{
+ if ( !m_bMouseOver )
+ return;
+
+ PostActionSignal( new KeyValues("BlueprintPanelEntered") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::OnCursorExited( void )
+{
+ if ( !m_bMouseOver )
+ return;
+
+ PostActionSignal( new KeyValues("BlueprintPanelExited") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::OnMousePressed(vgui::MouseCode code)
+{
+ if ( !m_bClickable || code != MOUSE_LEFT )
+ return;
+
+ PostActionSignal( new KeyValues("BlueprintPanelMousePressed") );
+
+ vgui::surface()->PlaySound( "UI/buttonclick.wav" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::OnMouseReleased(vgui::MouseCode code)
+{
+ if ( !m_bClickable || code != MOUSE_LEFT )
+ return;
+
+ PostActionSignal( new KeyValues("BlueprintPanelMouseReleased") );
+
+ vgui::surface()->PlaySound( "UI/buttonclickrelease.wav" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::OnMouseDoublePressed(vgui::MouseCode code)
+{
+ if ( !m_bClickable || code != MOUSE_LEFT )
+ return;
+
+ PostActionSignal( new KeyValues("BlueprintPanelMouseDoublePressed") );
+
+ vgui::surface()->PlaySound( "UI/buttonclickrelease.wav" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBlueprintPanel::OnCursorMoved( int x, int y )
+{
+ if ( !m_bClickable )
+ return;
+
+ // Add our own xpos/ypos offset
+ int iXPos;
+ int iYPos;
+ GetPos( iXPos, iYPos );
+ PostActionSignal( new KeyValues("BlueprintPanelCursorMoved", "x", x + iXPos, "y", y + iYPos) );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/blueprint_panel.h b/game/client/tf/vgui/blueprint_panel.h
new file mode 100644
index 0000000..dd71205
--- /dev/null
+++ b/game/client/tf/vgui/blueprint_panel.h
@@ -0,0 +1,61 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef BLUEPRINT_PANEL_H
+#define BLUEPRINT_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/Frame.h>
+#include "tf_shareddefs.h"
+#include "IconPanel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CBlueprintPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBlueprintPanel, vgui::EditablePanel );
+public:
+ CBlueprintPanel( vgui::Panel *parent, const char *name );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ void SetObjectInfo( const CObjectInfo* pNewInfo );
+ const CObjectInfo* GetObjectInfo( void ) { return m_pObjectInfo; }
+
+ // Button functionality
+ void SetActAsButton( bool bClickable, bool bMouseOver );
+ virtual void OnCursorEntered();
+ virtual void OnCursorExited();
+ virtual void OnMousePressed(vgui::MouseCode code);
+ virtual void OnMouseDoublePressed(vgui::MouseCode code);
+ virtual void OnMouseReleased(vgui::MouseCode code);
+ MESSAGE_FUNC_INT_INT( OnCursorMoved, "OnCursorMoved", x, y );
+
+ void SetInStack( bool bVal ) { m_bInStack = bVal; }
+ bool IsInStack( void ) { return m_bInStack; }
+
+ vgui::Label *m_pItemNameLabel;
+ vgui::Label *m_pItemCostLabel;
+ char m_pszCost[8];
+
+ CIconPanel *m_pMetalIcon;
+ CIconPanel *m_pIcon;
+ CIconPanel *m_pBackground;
+
+ const CObjectInfo* m_pObjectInfo;
+
+ bool m_bClickable;
+ bool m_bMouseOver;
+
+ bool m_bInStack;
+};
+
+#endif // BLUEPRINT_PANEL_H
diff --git a/game/client/tf/vgui/character_info_panel.cpp b/game/client/tf/vgui/character_info_panel.cpp
new file mode 100644
index 0000000..6737a90
--- /dev/null
+++ b/game/client/tf/vgui/character_info_panel.cpp
@@ -0,0 +1,864 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "character_info_panel.h"
+#include "tf_statsummary.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui/IInput.h"
+#include "baseviewport.h"
+#include "iclientmode.h"
+#include "charinfo_loadout_subpanel.h"
+#include "charinfo_armory_subpanel.h"
+#include "ienginevgui.h"
+#include "tf_hud_statpanel.h"
+#include "c_tf_player.h"
+#include "tf_item_inventory.h"
+#include "econ_notifications.h"
+#include <vgui/ILocalize.h>
+#include <vgui_controls/AnimationController.h>
+#include "econ_ui.h"
+#include "c_tf_gamestats.h"
+#include "tf_item_pickup_panel.h"
+#include "store/v1/tf_store_panel.h"
+#include "store/v2/tf_store_panel2.h"
+#include "store/tf_store.h"
+#include "tf_matchmaking_dashboard.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+static vgui::DHANDLE<CCharacterInfoPanel> g_CharInfoPanel;
+IEconRootUI* EconUI( void )
+{
+ if (!g_CharInfoPanel.Get())
+ {
+ g_CharInfoPanel = new CCharacterInfoPanel( NULL );
+ g_CharInfoPanel->MakeReadyForUse();
+ g_CharInfoPanel->InvalidateLayout( false, true );
+ }
+ return g_CharInfoPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent );
+
+//-----------------------------------------------------------------------------
+// Purpose: Basic help dialog
+//-----------------------------------------------------------------------------
+CCharacterInfoPanel::CCharacterInfoPanel( Panel *parent ) : PropertyDialog(parent, "character_info")
+{
+ // Character info is parented to the game UI panel
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ // We don't want the gameui to delete us, or things get messy
+ SetAutoDelete( false );
+
+ SetMoveable( false );
+ SetSizeable( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ // Character loadouts
+ m_pLoadoutPanel = new CCharInfoLoadoutSubPanel(this);
+ m_pLoadoutPanel->AddActionSignalTarget( this );
+ AddPage( m_pLoadoutPanel, "#Loadout");
+
+ // Stat summary
+ CTFStatsSummaryPanel *pStatSummaryPanel = new CTFStatsSummaryPanel(this);
+ pStatSummaryPanel->SetupForEmbedded();
+ AddPage( pStatSummaryPanel, "#Stats");
+ CTFStatPanel *pStatPanel = GET_HUDELEMENT( CTFStatPanel );
+ if ( pStatPanel )
+ {
+ // Ask for our embedded stat summary be updated immediately
+ pStatPanel->UpdateStatSummaryPanel();
+ }
+
+ // Achievements
+ //AddPage(new CCharacterInfoSubAchievements(this), "#Achievements");
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_pLoadoutPanel->SetVisible( false );
+
+ m_pNotificationsPresentPanel = NULL;
+ m_bPreventClosure = false;
+ m_iClosePanel = ECONUI_BASEUI;
+ m_iDefaultTeam = TF_TEAM_RED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCharacterInfoPanel::~CCharacterInfoPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/CharInfoPanel.res" );
+
+ SetOKButtonVisible(false);
+ SetCancelButtonVisible(false);
+
+ m_pNotificationsPresentPanel = FindChildByName( "NotificationsPresentPanel" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::ShowPanel(bool bShow)
+{
+ m_bPreventClosure = false;
+
+ // Keep the MM dashboard on top of us
+ bShow ? GetMMDashboardParentManager()->PushModalFullscreenPopup( this )
+ : GetMMDashboardParentManager()->PopModalFullscreenPopup( this );
+
+ if ( bShow )
+ {
+ if ( GetPropertySheet()->GetActivePage() != m_pLoadoutPanel )
+ {
+ GetPropertySheet()->SetActivePage( m_pLoadoutPanel );
+ }
+ else
+ {
+ // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it
+ ivgui()->PostMessage( m_pLoadoutPanel->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() );
+ }
+
+ //InvalidateLayout( false, true );
+ Activate();
+
+ int iClass = m_pLoadoutPanel->GetCurrentClassIndex();
+ OpenLoadoutToClass( iClass, false );
+ }
+ else
+ {
+ PostMessage( m_pLoadoutPanel, new KeyValues("CancelSelection") );
+ }
+
+ bool bWasVisible = IsVisible() && m_pLoadoutPanel->IsVisible();
+ SetVisible( bShow );
+ if ( bWasVisible && !bShow )
+ {
+ m_pLoadoutPanel->OnCharInfoClosing();
+
+ // Clear this out so it doesn't affect anything the next time the econ UI is opened
+ m_iClosePanel = ECONUI_BASEUI;
+ m_iDefaultTeam = TF_TEAM_RED;
+ }
+ m_pLoadoutPanel->SetVisible( bShow );
+
+ // When we first appear, if we're on a server that couldn't get our loadout, show the failure dialog.
+ if ( !bWasVisible && bShow )
+ {
+ if ( engine->IsInGame() )
+ {
+ C_TFPlayer *pLocal = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocal && pLocal->m_Shared.IsLoadoutUnavailable() )
+ {
+ OpenServerNotConnectedToSteamDialog( this );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ if ( m_bPreventClosure )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ }
+ else
+ {
+ ShowPanel( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::Close()
+{
+ ShowPanel( false );
+
+ PostMessage( m_pLoadoutPanel, new KeyValues("CharInfoClosing") );
+
+ // If we're connected to a game server, we also close the game UI.
+ if ( engine->IsInGame() )
+ {
+ bool bClose = true;
+ if ( m_bCheckForRoomOnExit )
+ {
+ // Check to make sure the player has room for all his items. If not, bring up the discard panel. Otherwise, go away.
+ // We need to do this to catch players who used the "Change Loadout" button in the pickup panel, and may be out of room.
+ bClose = !TFInventoryManager()->CheckForRoomAndForceDiscard();
+ }
+
+ if ( bClose )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+ }
+
+ // Notify any listeners that we're closed
+ NotifyListenersOfCloseEvent();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::NotifyListenersOfCloseEvent()
+{
+ FOR_EACH_VEC( m_vecOnCloseListeners, i )
+ {
+ if ( m_vecOnCloseListeners[i].Get() )
+ {
+ PostMessage( m_vecOnCloseListeners[i].Get(), new KeyValues( "EconUIClosed" ) );
+ }
+ }
+
+ // Clear that motherfucker out
+ m_vecOnCloseListeners.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "back" ) )
+ {
+ // If we're at the base loadout page, or if we want to force it, close the dialog completely...
+ // NOTE: Right now we don't support closing from the item selection screen.
+ const int iShowingPanel = m_pLoadoutPanel->GetShowingPanel();
+ const int iCurrentClassIndex = m_pLoadoutPanel->GetCurrentClassIndex();
+ const bool bIsInSelectionPanel = iShowingPanel == CHAP_LOADOUT && m_pLoadoutPanel->GetClassLoadoutPanel()->IsInSelectionPanel();
+ const bool bNoClass = iCurrentClassIndex == TF_CLASS_UNDEFINED;
+ const bool bAtClosePanel = !bIsInSelectionPanel &&
+ ( ( iShowingPanel == m_iClosePanel && bNoClass ) || ( iShowingPanel == CHAP_LOADOUT && -m_iClosePanel == iCurrentClassIndex ) );
+ const bool bAtBaseLoadoutPage = iShowingPanel == CHAP_LOADOUT && bNoClass;
+ if ( bAtClosePanel || bAtBaseLoadoutPage )
+ {
+ Close();
+ }
+ // In the item selection panel?
+ else if ( bIsInSelectionPanel )
+ {
+ m_pLoadoutPanel->GetClassLoadoutPanel()->GetItemSelectionPanel()->OnBackPressed();
+ }
+ // In any other panel, just go back.
+ else
+ {
+ ShowPanel( true );
+ }
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OpenLoadoutToClass( int iClassIndex, bool bOpenClassLoadout )
+{
+ Assert(iClassIndex >= TF_CLASS_UNDEFINED && iClassIndex < TF_CLASS_COUNT);
+ m_pLoadoutPanel->SetClassIndex( iClassIndex, bOpenClassLoadout );
+ m_pLoadoutPanel->SetTeamIndex( m_iDefaultTeam );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OpenLoadoutToBackpack( void )
+{
+ m_pLoadoutPanel->OpenToBackpack();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OpenLoadoutToCrafting( void )
+{
+ m_pLoadoutPanel->OpenToCrafting();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OpenLoadoutToArmory( void )
+{
+ m_pLoadoutPanel->OpenToArmory();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OnOpenArmoryDirect( KeyValues *data )
+{
+ int iItemDef = data->GetInt( "itemdef", 0 );
+ m_pLoadoutPanel->OpenToArmory( iItemDef );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE )
+ {
+ if ( !m_bPreventClosure )
+ {
+ OnCommand( "back" );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OnKeyCodePressed(vgui::KeyCode code)
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if ( nButtonCode == KEY_XBUTTON_B )
+ {
+ if ( !m_bPreventClosure )
+ {
+ OnCommand( "back" );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::OnThink()
+{
+ bool bShouldBeVisible = NotificationQueue_GetNumNotifications() != 0;
+ if ( m_pNotificationsPresentPanel != NULL && m_pNotificationsPresentPanel->IsVisible() != bShouldBeVisible )
+ {
+ m_pNotificationsPresentPanel->SetVisible( bShouldBeVisible );
+ if ( bShouldBeVisible )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlink" );
+ }
+ else
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlinkStop" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IEconRootUI *CCharacterInfoPanel::OpenEconUI( int iDirectToPage, bool bCheckForInventorySpaceOnExit )
+{
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ ShowPanel( true );
+
+ if ( iDirectToPage == ECONUI_BACKPACK )
+ {
+ OpenLoadoutToBackpack();
+ }
+ else if ( iDirectToPage == ECONUI_CRAFTING )
+ {
+ OpenLoadoutToCrafting();
+ }
+ else if ( iDirectToPage == ECONUI_ARMORY )
+ {
+ OpenLoadoutToArmory();
+ }
+ else if ( iDirectToPage < 0 )
+ {
+ // Negative numbers go directly to the class loadout
+ OpenLoadoutToClass( -(iDirectToPage), true );
+ }
+
+ SetCheckForRoomOnExit( bCheckForInventorySpaceOnExit );
+
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::CloseEconUI( void )
+{
+ if ( IsVisible() )
+ {
+ ShowPanel( false );
+ NotifyListenersOfCloseEvent();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CCharacterInfoPanel::IsUIPanelVisible( EconBaseUIPanels_t iPanel )
+{
+ if ( !IsVisible() )
+ return false;
+
+ switch ( iPanel )
+ {
+ case ECONUI_BACKPACK:
+ return (GetBackpackPanel() && GetBackpackPanel()->IsVisible());
+
+ case ECONUI_CRAFTING:
+ return (GetCraftingPanel() && GetCraftingPanel()->IsVisible());
+
+ case ECONUI_ARMORY:
+ return (GetArmoryPanel() && GetArmoryPanel()->IsVisible());
+
+ case ECONUI_TRADING:
+ break;
+
+ default:
+ Assert(0);
+ break;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_CharInfo( const CCommand &args )
+{
+ EconUI()->OpenEconUI();
+}
+ConCommand open_charinfo( "open_charinfo", Open_CharInfo, "Open the character info panel", FCVAR_NONE );
+
+void CCharacterInfoPanel::SetPreventClosure( bool bPrevent )
+{
+ m_bPreventClosure = bPrevent;
+
+ Panel* pBackButton = FindChildByName( "BackButton" );
+ if ( pBackButton )
+ {
+ pBackButton->SetEnabled( !bPrevent );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_CharInfoDirect( const CCommand &args )
+{
+ // If we're in-game, start by opening the class we're currently playing
+ int iClass = TF_CLASS_UNDEFINED;
+ if ( engine->IsInGame() )
+ {
+ C_TFPlayer *pLocal = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocal )
+ {
+ iClass = -(pLocal->m_Shared.GetDesiredPlayerClassIndex());
+ if ( iClass == TF_CLASS_UNDEFINED )
+ {
+ iClass = -(pLocal->GetPlayerClass()->GetClassIndex());
+ }
+ }
+ }
+
+ // override with command arg
+ if ( args.ArgC() > 1 )
+ {
+ iClass = -atoi( args.Arg( 1 ) );
+ }
+
+ EconUI()->OpenEconUI( iClass );
+}
+ConCommand open_charinfo_direct( "open_charinfo_direct", Open_CharInfoDirect, "Open the character info panel directly to the class you're currently playing.", FCVAR_NONE );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_CharInfoBackpack( const CCommand &args )
+{
+ EconUI()->OpenEconUI( ECONUI_BACKPACK );
+}
+ConCommand open_charinfo_backpack( "open_charinfo_backpack", Open_CharInfoBackpack, "Open the character info panel directly to backpack.", FCVAR_NONE );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_CharInfoCrafting( const CCommand &args )
+{
+ EconUI()->OpenEconUI( ECONUI_CRAFTING );
+}
+ConCommand open_charinfo_crafting( "open_charinfo_crafting", Open_CharInfoCrafting, "Open the character info panel directly to crafting screen.", FCVAR_NONE );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_CharInfoArmory( const CCommand &args )
+{
+ EconUI()->OpenEconUI( ECONUI_ARMORY );
+}
+ConCommand open_charinfo_armory( "open_charinfo_armory", Open_CharInfoArmory, "Open the character info panel directly to armory.", FCVAR_NONE );
+
+
+//================================================================================================================================
+// NOT CONNECTED TO STEAM WARNING DIALOG
+//================================================================================================================================
+static vgui::DHANDLE<CServerNotConnectedToSteamDialog> g_ServerNotConnectedPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CServerNotConnectedToSteamDialog::CServerNotConnectedToSteamDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "ServerNotConnectedToSteamDialog" )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CServerNotConnectedToSteamDialog::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // load control settings...
+ LoadControlSettings( "resource/UI/ServerNotConnectedToSteam.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CServerNotConnectedToSteamDialog::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent )
+{
+ if (!g_ServerNotConnectedPanel.Get())
+ {
+ g_ServerNotConnectedPanel = vgui::SETUP_PANEL( new CServerNotConnectedToSteamDialog( pParent, NULL ) );
+ }
+ g_ServerNotConnectedPanel->InvalidateLayout( false, true );
+
+ g_ServerNotConnectedPanel->SetVisible( true );
+ g_ServerNotConnectedPanel->MakePopup();
+ g_ServerNotConnectedPanel->MoveToFront();
+ g_ServerNotConnectedPanel->SetKeyBoardInputEnabled(true);
+ g_ServerNotConnectedPanel->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_ServerNotConnectedPanel );
+ return g_ServerNotConnectedPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBackpackPanel *CCharacterInfoPanel::GetBackpackPanel( void )
+{
+ return m_pLoadoutPanel->GetBackpackPanel();
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCraftingPanel *CCharacterInfoPanel::GetCraftingPanel( void )
+{
+ return m_pLoadoutPanel->GetCraftingPanel();
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CArmoryPanel *CCharacterInfoPanel::GetArmoryPanel( void )
+{
+ return m_pLoadoutPanel->GetArmoryPanel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::Gamestats_ItemTransaction( int eventID, CEconItemView *item, const char *pszReason, int iQuality )
+{
+ C_CTF_GameStats.Event_ItemTransaction( eventID, item, pszReason, iQuality );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::Gamestats_Store( int eventID, CEconItemView* item, const char* panelName, int classId,
+ const cart_item_t* cartItem, int checkoutAttempts, const char* storeError, int totalPrice, int currencyCode )
+{
+ C_CTF_GameStats.Event_Store( eventID, item, panelName, classId, cartItem, checkoutAttempts, storeError, totalPrice, currencyCode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::SetExperimentValue( uint64 experimentValue )
+{
+ C_CTF_GameStats.SetExperimentValue( experimentValue );
+}
+
+static vgui::DHANDLE<CTFItemPickupPanel> g_TFItemPickupPanel;
+static vgui::DHANDLE<CTFItemDiscardPanel> g_TFItemDiscardPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemPickupPanel *CCharacterInfoPanel::OpenItemPickupPanel( void )
+{
+ if (!g_TFItemPickupPanel.Get())
+ {
+ g_TFItemPickupPanel = vgui::SETUP_PANEL( new CTFItemPickupPanel( NULL ) );
+ g_TFItemPickupPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_TFItemPickupPanel->ShowPanel( true );
+
+ return g_TFItemPickupPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemDiscardPanel *CCharacterInfoPanel::OpenItemDiscardPanel( void )
+{
+ if (!g_TFItemDiscardPanel.Get())
+ {
+ g_TFItemDiscardPanel = vgui::SETUP_PANEL( new CTFItemDiscardPanel( NULL ) );
+ g_TFItemDiscardPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_TFItemDiscardPanel->ShowPanel( true );
+
+ return g_TFItemDiscardPanel;
+}
+
+static vgui::DHANDLE<CTFBaseStorePanel> g_StorePanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::CreateStorePanel( void )
+{
+ // Clean up previous store panel?
+ if ( g_StorePanel.Get() != NULL )
+ {
+ g_StorePanel->MarkForDeletion();
+ }
+
+ // Create the store panel
+ CTFBaseStorePanel *pStorePanel = NULL;
+ if ( ShouldUseNewStore() )
+ {
+ pStorePanel = new CTFStorePanel2( NULL );
+ }
+ else
+ {
+ pStorePanel = new CTFStorePanel1( NULL );
+ }
+
+ g_StorePanel = vgui::SETUP_PANEL( pStorePanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel *CCharacterInfoPanel::OpenStorePanel( int iItemDef, bool bAddToCart )
+{
+ // Make sure we've got the appropriate connections to Steam
+ if ( !steamapicontext || !steamapicontext->SteamUtils() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_SteamRequired", true, false );
+ return NULL;
+ }
+
+ if ( !steamapicontext->SteamUtils()->IsOverlayEnabled() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_OverlayRequired", true, false );
+ return NULL;
+ }
+
+ if ( !CStorePanel::IsPricesheetLoaded() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_Loading", false, false );
+
+ CStorePanel::SetShouldShowWarnings( true );
+ CStorePanel::RequestPricesheet();
+ return NULL;
+ }
+
+ if ( !g_StorePanel )
+ return NULL;
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+
+ if ( iItemDef )
+ {
+ g_StorePanel->StartAtItemDef( iItemDef, bAddToCart );
+ }
+
+ g_StorePanel->ShowPanel( true );
+
+ return g_StorePanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel *CCharacterInfoPanel::GetStorePanel( void )
+{
+ return g_StorePanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::AddPanelCloseListener( vgui::Panel *pListener )
+{
+ if ( !pListener )
+ return;
+
+ VPanelHandle hPanel;
+ hPanel.Set( pListener->GetVPanel() );
+ m_vecOnCloseListeners.AddToHead( hPanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharacterInfoPanel::SetClosePanel( int iPanel )
+{
+ AssertMsg( ( iPanel < 0 && IsValidTFPlayerClass( -iPanel ) ) ||
+ ( iPanel >= ECONUI_FIRST_PANEL && iPanel <= ECONUI_LAST_PANEL ),
+ "Panel out of range!"
+ );
+ m_iClosePanel = iPanel;
+}
+
+void CCharacterInfoPanel::SetDefaultTeam( int iTeam )
+{
+ AssertMsg( iTeam == TF_TEAM_RED || iTeam == TF_TEAM_BLUE, "Invalid team" );
+ m_iDefaultTeam = iTeam;
+}
+
+//================================================================================================================================
+// NOT CONNECTED TO STEAM WARNING DIALOG
+//================================================================================================================================
+static vgui::DHANDLE<CCheatDetectionDialog> g_CheatDetectionDialog;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCheatDetectionDialog::CCheatDetectionDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "CheatDetectionDialog" )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCheatDetectionDialog::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // load control settings...
+ LoadControlSettings( "resource/UI/CheatDetectionDialog.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCheatDetectionDialog::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCheatDetectionDialog *OpenCheatDetectionDialog( vgui::Panel *pParent, const char *pszCheatMessage )
+{
+ if (!g_CheatDetectionDialog.Get())
+ {
+ g_CheatDetectionDialog = vgui::SETUP_PANEL( new CCheatDetectionDialog( pParent, NULL ) );
+ }
+ g_CheatDetectionDialog->InvalidateLayout( false, true );
+
+ g_CheatDetectionDialog->SetVisible( true );
+ g_CheatDetectionDialog->MakePopup();
+ g_CheatDetectionDialog->MoveToFront();
+ g_CheatDetectionDialog->SetKeyBoardInputEnabled(true);
+ g_CheatDetectionDialog->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_CheatDetectionDialog );
+ g_CheatDetectionDialog->SetDialogVariable( "reason", g_pVGuiLocalize->Find( pszCheatMessage ) );
+ return g_CheatDetectionDialog;
+}
diff --git a/game/client/tf/vgui/character_info_panel.h b/game/client/tf/vgui/character_info_panel.h
new file mode 100644
index 0000000..8f435cd
--- /dev/null
+++ b/game/client/tf/vgui/character_info_panel.h
@@ -0,0 +1,135 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CHARACTER_INFO_PANEL_H
+#define CHARACTER_INFO_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "econ_ui.h"
+#include "vgui_controls/PropertyDialog.h"
+#include "tf_shareddefs.h"
+#include "GameEventListener.h"
+#include "vgui_controls/Panel.h"
+#include "vgui_controls/PHandle.h"
+
+class CCharInfoLoadoutSubPanel;
+class CArmoryPanel;
+class CBackpackPanel;
+class CCraftingPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CServerNotConnectedToSteamDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CServerNotConnectedToSteamDialog, vgui::EditablePanel );
+
+public:
+ CServerNotConnectedToSteamDialog( vgui::Panel *pParent, const char *pElementName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void OnCommand( const char *command );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CCheatDetectionDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CCheatDetectionDialog, vgui::EditablePanel );
+
+public:
+ CCheatDetectionDialog( vgui::Panel *pParent, const char *pElementName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void OnCommand( const char *command );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CCharacterInfoPanel : public vgui::PropertyDialog, public IEconRootUI, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CCharacterInfoPanel, vgui::PropertyDialog );
+public:
+ CCharacterInfoPanel( Panel *parent );
+ virtual ~CCharacterInfoPanel();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow );
+ virtual void OnKeyCodeTyped(vgui::KeyCode code) OVERRIDE;
+ virtual void OnKeyCodePressed(vgui::KeyCode code) OVERRIDE;
+ virtual void OnThink();
+
+ void OpenLoadoutToClass( int iClassIndex, bool bOpenClassLoadout );
+ void OpenLoadoutToBackpack( void );
+ void OpenLoadoutToCrafting( void );
+ void OpenLoadoutToArmory( void );
+ void SetCheckForRoomOnExit( bool bCheck ) { m_bCheckForRoomOnExit = bCheck; }
+
+ void FireGameEvent( IGameEvent *event );
+
+ CArmoryPanel *GetArmoryPanel( void );
+
+ MESSAGE_FUNC_PARAMS( OnOpenArmoryDirect, "OpenArmoryDirect", data );
+
+ //---------------------------------------
+ // IEconRootUI
+ virtual IEconRootUI *OpenEconUI( int iDirectToPage = 0, bool bCheckForInventorySpaceOnExit = false );
+ virtual void CloseEconUI( void );
+ virtual bool IsUIPanelVisible( EconBaseUIPanels_t iPanel );
+ virtual void SetPreventClosure( bool bPrevent ) OVERRIDE;
+
+ // Sub panel access.
+ // These are panels that are parented to the root EconUI.
+ virtual CBackpackPanel *GetBackpackPanel( void );
+ virtual CCraftingPanel *GetCraftingPanel( void );
+
+ // Gamestats access
+ virtual void Gamestats_ItemTransaction( int eventID, CEconItemView *item, const char *pszReason = NULL, int iQuality = 0 );
+ virtual void Gamestats_Store( int eventID, CEconItemView* item=NULL, const char* panelName=NULL,
+ int classId=0, const cart_item_t* in_cartItem=NULL, int in_checkoutAttempts=0, const char* storeError=NULL, int in_totalPrice=0, int in_currencyCode=0 );
+ virtual void SetExperimentValue( uint64 experimentValue );
+
+ // Open separate economy panels (they're not parented to the root EconUI)
+ // This is here so that games can customize the implementation of these panels.
+ virtual CItemPickupPanel *OpenItemPickupPanel( void );
+ virtual CItemDiscardPanel *OpenItemDiscardPanel( void );
+ virtual void CreateStorePanel( void );
+ virtual CStorePanel *OpenStorePanel( int iItemDef, bool bAddToCart );
+ virtual CStorePanel *GetStorePanel( void );
+
+ // When the root UI is closed, send an "EconUIClosed" message to pListener.
+ virtual void AddPanelCloseListener( vgui::Panel *pListener );
+
+ // The panel at which we want back to actually close the UI - defaults to the root panel - a negative value can be passed in for class loadout panels
+ virtual void SetClosePanel( int iPanel );
+
+ // Call this to set which team the class loadout should display
+ virtual void SetDefaultTeam( int iTeam );
+
+private:
+ void Close();
+ void NotifyListenersOfCloseEvent();
+
+ vgui::Panel *m_pNotificationsPresentPanel;
+ CCharInfoLoadoutSubPanel *m_pLoadoutPanel;
+ bool m_bCheckForRoomOnExit;
+ bool m_bPreventClosure;
+ int m_iClosePanel;
+ int m_iDefaultTeam;
+
+ CUtlVector< vgui::VPanelHandle > m_vecOnCloseListeners;
+};
+
+CCheatDetectionDialog *OpenCheatDetectionDialog( vgui::Panel *pParent, const char *pszCheatMessage );
+
+#endif // CHARACTER_INFO_PANEL_H
diff --git a/game/client/tf/vgui/charinfo_armory_subpanel.cpp b/game/client/tf/vgui/charinfo_armory_subpanel.cpp
new file mode 100644
index 0000000..ceab6f7
--- /dev/null
+++ b/game/client/tf/vgui/charinfo_armory_subpanel.cpp
@@ -0,0 +1,945 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "charinfo_armory_subpanel.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "c_tf_player.h"
+#include "c_tf_gamestats.h"
+#include "gamestringpool.h"
+#include "tf_item_inventory.h"
+#include "econ_item_system.h"
+#include "iachievementmgr.h"
+#include "store/store_panel.h"
+#include "character_info_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+ConVar tf_explanations_charinfo_armory_panel( "tf_explanations_charinfo_armory_panel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." );
+
+const char *g_szArmoryFilterStrings[ARMFILT_TOTAL] =
+{
+ "#ArmoryFilter_AllItems", // ARMFILT_ALL_ITEMS.
+ "#ArmoryFilter_Weapons", // ARMFILT_WEAPONS,
+ "#ArmoryFilter_Headgear", // ARMFILT_HEADGEAR,
+ "#ArmoryFilter_MiscItems", // ARMFILT_MISCITEMS,
+ "#ArmoryFilter_ActionItems", // ARMFILT_ACTIONITEMS,
+ "#ArmoryFilter_CraftItems", // ARMFILT_CRAFTITEMS,
+ "#ArmoryFilter_Tools", // ARMFILT_TOOLS,
+ "#ArmoryFilter_AllClass", // ARMFILT_CLASS_ALL,
+ "#ArmoryFilter_Scout", // ARMFILT_CLASS_SCOUT,
+ "#ArmoryFilter_Sniper", // ARMFILT_CLASS_SNIPER,
+ "#ArmoryFilter_Soldier", // ARMFILT_CLASS_SOLDIER,
+ "#ArmoryFilter_Demoman", // ARMFILT_CLASS_DEMOMAN,
+ "#ArmoryFilter_Medic", // ARMFILT_CLASS_MEDIC,
+ "#ArmoryFilter_Heavy", // ARMFILT_CLASS_HEAVY,
+ "#ArmoryFilter_Pyro", // ARMFILT_CLASS_PYRO,
+ "#ArmoryFilter_Spy", // ARMFILT_CLASS_SPY,
+ "#ArmoryFilter_Engineer", // ARMFILT_CLASS_ENGINEER,
+ "#ArmoryFilter_Donationitems", // ARMFILT_DONATIONITEMS,
+
+ "", // ARMFILT_NUM_IN_DROPDOWN
+ "Not Used", // ARMFILT_CUSTOM
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CArmoryPanel::CArmoryPanel(Panel *parent, const char *panelName) : vgui::EditablePanel( parent, panelName )
+{
+ m_pSelectedItemModelPanel = new CItemModelPanel( this, "SelectedItemModelPanel" );
+ m_pSelectedItemImageModelPanel = new CItemModelPanel( this, "SelectedItemImageModelPanel" );
+ m_pThumbnailModelPanelKVs = NULL;
+ m_bReapplyItemKVs = false;
+ m_CurrentFilter = ARMFILT_ALL_ITEMS;
+ m_OldFilter = ARMFILT_ALL_ITEMS;
+ m_iFilterPage = 0;
+ m_pNextPageButton = NULL;
+ m_pPrevPageButton = NULL;
+ m_pViewSetButton = NULL;
+ m_pStoreButton = NULL;
+ m_bAllowGotoStore = false;
+
+ m_pDataPanel = new vgui::EditablePanel( this, "DataPanel" );
+ m_pDataTextRichText = NULL;
+
+ m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" );
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+ m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE );
+
+ m_pFilterComboBox = new vgui::ComboBox( this, "FilterComboBox", ARMFILT_NUM_IN_DROPDOWN, false );
+ m_pFilterComboBox->AddActionSignalTarget( this );
+
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colThumbnailBG, "thumbnail_bgcolor" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colThumbnailBGMouseover, "thumbnail_bgcolor_mouseover" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colThumbnailBGSelected, "thumbnail_bgcolor_selected" );
+
+ m_bEventLogging = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CArmoryPanel::~CArmoryPanel()
+{
+ if ( m_pThumbnailModelPanelKVs )
+ {
+ m_pThumbnailModelPanelKVs->deleteThis();
+ m_pThumbnailModelPanelKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/CharInfoArmorySubPanel.res" );
+
+ m_bReapplyItemKVs = true;
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+
+ m_pDataTextRichText = dynamic_cast<CEconItemDetailsRichText*>( m_pDataPanel->FindChildByName( "Data_TextRichText" ) );
+ m_pNextPageButton = dynamic_cast<CExButton*>( FindChildByName("NextPageButton") );
+ m_pPrevPageButton = dynamic_cast<CExButton*>( FindChildByName("PrevPageButton") );
+ m_pViewSetButton = dynamic_cast<CExButton*>( FindChildByName("ViewSetButton") );
+ m_pStoreButton = dynamic_cast<CExButton*>( FindChildByName("StoreButton") );
+
+ m_pDataTextRichText->SetURLClickedHandler( this );
+
+ m_colSetName = GetSchemeColor( "ItemSetName", Color(255, 255, 255, 255), pScheme );
+
+ SetupComboBox( NULL );
+ UpdateSelectedItem();
+ m_pFilterComboBox->SetBorder( NULL );
+
+ m_pMouseOverItemPanel->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::SetupComboBox( const char *pszCustomAddition )
+{
+ m_pFilterComboBox->RemoveAll();
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pFilterComboBox->SetFont( hFont );
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ for ( int i = 0; i < ARMFILT_NUM_IN_DROPDOWN; i++ )
+ {
+ pKeyValues->SetInt( "setfilter", i );
+ m_pFilterComboBox->AddItem( g_szArmoryFilterStrings[i], pKeyValues );
+ }
+
+ if ( pszCustomAddition )
+ {
+ pKeyValues->SetInt( "setfilter", ARMFILT_CUSTOM );
+ m_pFilterComboBox->AddItem( g_pVGuiLocalize->Find( pszCustomAddition ), pKeyValues );
+
+ // Start with the custom filter selected
+ m_pFilterComboBox->SetNumberOfEditLines( ARMFILT_NUM_IN_DROPDOWN + 1 );
+ m_pFilterComboBox->ActivateItemByRow( ARMFILT_NUM_IN_DROPDOWN );
+ }
+ else
+ {
+ m_pFilterComboBox->SetNumberOfEditLines( ARMFILT_NUM_IN_DROPDOWN );
+ m_pFilterComboBox->ActivateItemByRow( 0 );
+ }
+
+ pKeyValues->deleteThis();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "thumbnail_modelpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pThumbnailModelPanelKVs )
+ {
+ m_pThumbnailModelPanelKVs->deleteThis();
+ }
+ m_pThumbnailModelPanelKVs = new KeyValues("thumbnail_modelpanels_kv");
+ pItemKV->CopySubkeys( m_pThumbnailModelPanelKVs );
+ }
+}
+
+ // C_CTF_GameStats.Event_Item( IE_ARMORY_EXITED );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnShowPanel( void )
+{
+ InvalidateLayout( true, true );
+
+ m_pMouseOverItemPanel->SetVisible( false );
+
+ UpdateSelectedItem();
+
+ // If this is the first time we've opened the armory, start the armory explanations
+ if ( !tf_explanations_charinfo_armory_panel.GetBool() && ShouldShowExplanations() )
+ {
+ m_flStartExplanationsAt = engine->Time() + 0.5;
+ }
+
+ SetVisible( true );
+
+ if ( !m_bEventLogging )
+ {
+ C_CTF_GameStats.Event_Catalog( IE_ARMORY_ENTERED );
+ m_bEventLogging = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Select the given item and jump to the appropriate page
+//-----------------------------------------------------------------------------
+void CArmoryPanel::JumpToItem( int iItemDef, armory_filters_t nFilter )
+{
+ // Setup filter and select iItemDef
+ SetFilterTo( iItemDef, nFilter );
+
+ // If we have an item def, find out what page it's on and move to it
+ if ( iItemDef > 0 )
+ {
+ const CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( iItemDef );
+ if ( pDef )
+ {
+ // Attempt to get item def from armory remap parameter
+ int iArmoryRemap = pDef->GetArmoryRemap();
+ if ( iArmoryRemap > 0 )
+ {
+ iItemDef = iArmoryRemap;
+
+ SetSelectedItem( iItemDef );
+ }
+
+ // If the item specified is a stock item, find the upgradeable version of it instead
+ else if ( pDef->GetQuality() == AE_NORMAL )
+ {
+ // Prepend the upgradeable string
+ char szTmpName[256];
+ Q_snprintf( szTmpName, sizeof(szTmpName), "Upgradeable %s", pDef->GetDefinitionName() );
+
+ pDef = ItemSystem()->GetStaticDataForItemByName( szTmpName );
+ if ( pDef )
+ {
+ iItemDef = pDef->GetDefinitionIndex();
+ }
+ }
+ }
+
+ // Find and select the page iItemDef is on
+ FOR_EACH_VEC( m_FilteredItemList, i )
+ {
+ if ( m_FilteredItemList[i] == (item_definition_index_t)iItemDef )
+ {
+ int iThumbnailsPerPage = (m_iThumbnailRows * m_iThumbnailColumns);
+ m_iFilterPage = floor( (float)i / (float)iThumbnailsPerPage );
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Default behavior - select first item on first page
+ m_iFilterPage = 0;
+ SetSelectedItem( m_FilteredItemList[0] );
+ }
+
+ UpdateItemList();
+ UpdateSelectedItem();
+
+ m_pMouseOverItemPanel->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Show the armory with one of the default filters set
+//-----------------------------------------------------------------------------
+void CArmoryPanel::ShowPanel( int iItemDef, armory_filters_t nFilter )
+{
+ JumpToItem( iItemDef, nFilter );
+ OnShowPanel();
+ m_pFilterComboBox->ActivateItemByRow( 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Show the armory with a custom list of item definitions, and a custom filter string
+//-----------------------------------------------------------------------------
+void CArmoryPanel::ShowPanel( const char *pszFilterString, CUtlVector<item_definition_index_t> *vecItems )
+{
+ m_CustomFilteredList = *vecItems;
+ SetupComboBox( pszFilterString );
+ ShowPanel( 0, ARMFILT_CUSTOM );
+
+ // Move to the custom entry
+ m_pFilterComboBox->ActivateItemByRow( ARMFILT_NUM_IN_DROPDOWN );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnClosing()
+{
+ if ( m_bEventLogging )
+ {
+ C_CTF_GameStats.Event_Catalog( IE_ARMORY_EXITED );
+ m_bEventLogging = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "prevpage", 8 ) )
+ {
+ if ( m_iFilterPage > 0 )
+ {
+ m_iFilterPage--;
+ UpdateItemList();
+ UpdateSelectedItem();
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "nextpage", 8 ) )
+ {
+ int nMaxPages = MAX( 1, ceil(m_FilteredItemList.Count() / (float)(m_iThumbnailRows * m_iThumbnailColumns)) );
+ if ( m_iFilterPage < (nMaxPages-1) )
+ {
+ m_iFilterPage++;
+ UpdateItemList();
+ UpdateSelectedItem();
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "back", 4 ) )
+ {
+ PostMessage( GetParent(), new KeyValues("ArmoryClosed") );
+ return;
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( false, true );
+ SetTall( YRES(400) );
+ SetVisible( true );
+ }
+ else if ( !Q_stricmp( command, "openstore" ) )
+ {
+ // Only available in the loadout->catalog path. So we close down the character info, and move to the store.
+ // Bit of a hack.
+ EconUI()->CloseEconUI();
+
+ int iItemDef = m_SelectedItem.IsValid() ? m_SelectedItem.GetItemDefIndex() : 0;
+ EconUI()->OpenStorePanel( iItemDef, false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "wiki" ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ if ( IsVisible() && m_SelectedItem.IsValid() )
+ {
+ // Determine which language we should use
+ char uilanguage[ 64 ];
+ uilanguage[0] = 0;
+ engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
+ ELanguage iLang = PchLanguageToELanguage( uilanguage );
+
+ char szURL[512];
+ Q_snprintf( szURL, sizeof(szURL), "http://wiki.teamfortress.com/scripts/itemredirect.php?id=%d&lang=%s", m_SelectedItem.GetItemDefIndex(), GetLanguageICUName( iLang ) );
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL );
+
+ C_CTF_GameStats.Event_Catalog( IE_ARMORY_BROWSE_WIKI, NULL, &m_SelectedItem );
+ }
+ }
+ }
+ else if ( !Q_stricmp( command, "viewset" ) )
+ {
+ if ( m_SelectedItem.IsValid() )
+ {
+ const CEconItemSetDefinition *pItemSet = m_SelectedItem.GetStaticData()->GetItemSetDefinition();
+ if ( pItemSet )
+ {
+ m_CustomFilteredList.Purge();
+ FOR_EACH_VEC( pItemSet->m_iItemDefs, i )
+ {
+ m_CustomFilteredList.AddToTail( pItemSet->m_iItemDefs[i] );
+ }
+
+ SetupComboBox( pItemSet->m_pszLocalizedName );
+ SetFilterTo( m_SelectedItem.GetItemDefIndex(), ARMFILT_CUSTOM );
+ }
+ }
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::MoveItem( int iDelta )
+{
+ int iIdx = m_FilteredItemList.Find( m_SelectedItem.GetItemDefIndex() );
+ m_SelectedItem.Invalidate();
+
+ if ( iIdx != m_FilteredItemList.InvalidIndex() )
+ {
+ iIdx += iDelta;
+ if ( iIdx >= m_FilteredItemList.Count() )
+ {
+ iIdx = 0;
+ }
+ else if ( iIdx < 0 )
+ {
+ iIdx = m_FilteredItemList.Count() - 1;
+ }
+
+ if ( iIdx < m_FilteredItemList.Count() )
+ {
+ const CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_FilteredItemList[iIdx] );
+ if ( pDef )
+ {
+ SetSelectedItem( pDef->GetDefinitionIndex() );
+ }
+ }
+ }
+
+ UpdateSelectedItem();
+ }
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::PerformLayout( void )
+{
+ if ( m_bReapplyItemKVs )
+ {
+ m_bReapplyItemKVs = false;
+
+ if ( m_pThumbnailModelPanelKVs )
+ {
+ FOR_EACH_VEC( m_pThumbnailModelPanels, i )
+ {
+ m_pThumbnailModelPanels[i]->ApplySettings( m_pThumbnailModelPanelKVs );
+ SetBorderForItem( m_pThumbnailModelPanels[i], false );
+ m_pThumbnailModelPanels[i]->InvalidateLayout();
+ }
+ }
+ }
+
+ BaseClass::PerformLayout();
+
+ if ( m_pThumbnailModelPanels.Count() > 0 && m_iThumbnailColumns )
+ {
+ int iThumbnailModelWide = m_pThumbnailModelPanels[0]->GetWide();
+ int iThumbnailModelTall = m_pThumbnailModelPanels[0]->GetTall();
+ FOR_EACH_VEC( m_pThumbnailModelPanels, i )
+ {
+ if ( m_pThumbnailModelPanels[i]->HasItem() )
+ {
+ m_pThumbnailModelPanels[i]->SetVisible( true );
+ }
+
+ int iXPos = ( i % m_iThumbnailColumns );
+ int iYPos = ( i / m_iThumbnailColumns );
+
+ int iX = m_iThumbnailX + (m_iThumbnailDeltaX * iXPos) + (iThumbnailModelWide * iXPos);
+ int iY = m_iThumbnailY + (m_iThumbnailDeltaY * iYPos) + (iThumbnailModelTall * iYPos);
+ m_pThumbnailModelPanels[i]->SetPos( iX, iY );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::SetFilterTo( int iItemDef, armory_filters_t nFilter )
+{
+ m_FilteredItemList.Purge();
+ m_OldFilter = m_CurrentFilter;
+ m_CurrentFilter = nFilter;
+ m_iFilterPage = 0;
+
+ if ( nFilter == ARMFILT_CUSTOM )
+ {
+ m_FilteredItemList = m_CustomFilteredList;
+ }
+ else
+ {
+ // First, build a list of all the items that match the filter
+ const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap();
+ FOR_EACH_MAP( mapItemDefs, i )
+ {
+ const CTFItemDefinition *pDef = dynamic_cast<const CTFItemDefinition *>( mapItemDefs[i] );
+
+ // Never show:
+ // - Hidden items
+ // - Items that don't have fixed qualities
+ // - Normal quality items
+ // - Items that haven't asked to be shown
+ if ( pDef->IsHidden() || pDef->GetQuality() == k_unItemQuality_Any || pDef->GetQuality() == AE_NORMAL || !pDef->ShouldShowInArmory() )
+ continue;
+
+#ifdef DEBUG
+ // In Debug, make sure that every item shows up in a filter other than the All Items list
+ bool bFoundMatchingFilter = false;
+ for ( int iFilter = ARMFILT_WEAPONS; iFilter < ARMFILT_NUM_IN_DROPDOWN; iFilter++ )
+ {
+ if ( DefPassesFilter(pDef,(armory_filters_t)iFilter) )
+ {
+ bFoundMatchingFilter = true;
+ break;
+ }
+ }
+ Assert( bFoundMatchingFilter );
+#endif
+
+ if ( DefPassesFilter( pDef, m_CurrentFilter ) )
+ {
+ m_FilteredItemList.AddToTail( pDef->GetDefinitionIndex() );
+
+ if ( iItemDef == pDef->GetDefinitionIndex() )
+ {
+ SetSelectedItem( pDef->GetDefinitionIndex() );
+ }
+ }
+ }
+ }
+
+ // Make sure our current item is in the list
+ if ( m_SelectedItem.IsValid() )
+ {
+ if ( m_FilteredItemList.Find( m_SelectedItem.GetItemDefIndex() ) == m_FilteredItemList.InvalidIndex() )
+ {
+ m_SelectedItem.Invalidate();
+ }
+ }
+
+ UpdateItemList();
+
+ if ( m_CurrentFilter != m_OldFilter )
+ {
+ C_CTF_GameStats.Event_Catalog( IE_ARMORY_CHANGE_FILTER, g_szArmoryFilterStrings[m_CurrentFilter] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CArmoryPanel::DefPassesFilter( const CTFItemDefinition *pDef, armory_filters_t iFilter )
+{
+ bool bInList = false;
+
+ switch (iFilter)
+ {
+ case ARMFILT_ALL_ITEMS:
+ {
+ bInList = true;
+ break;
+ }
+
+ case ARMFILT_WEAPONS:
+ {
+ int iSlot = pDef->GetDefaultLoadoutSlot();
+ bInList = ( iSlot == LOADOUT_POSITION_PRIMARY || iSlot == LOADOUT_POSITION_SECONDARY || iSlot == LOADOUT_POSITION_MELEE );
+ break;
+ }
+
+ case ARMFILT_HEADGEAR:
+ {
+ bInList = (pDef->GetDefaultLoadoutSlot() == LOADOUT_POSITION_HEAD);
+ break;
+ }
+
+ case ARMFILT_MISCITEMS:
+ {
+ bInList = (pDef->GetDefaultLoadoutSlot() == LOADOUT_POSITION_MISC);
+ break;
+ }
+
+ case ARMFILT_ACTIONITEMS:
+ {
+ bInList = (pDef->GetDefaultLoadoutSlot() == LOADOUT_POSITION_ACTION);
+ break;
+ }
+
+ case ARMFILT_CRAFTITEMS:
+ {
+ bInList = pDef->GetItemClass() && ( !V_strcmp( pDef->GetItemClass(), "craft_item" ) || !V_strcmp( pDef->GetItemClass(), "class_token" ) || !V_strcmp( pDef->GetItemClass(), "slot_token" ) );
+ break;
+ }
+
+ case ARMFILT_TOOLS:
+ {
+ // For now, put the supply crates into the tool list, since it's the only item that shows up in no other lists
+ bInList = pDef->GetItemClass() && ( !V_strcmp( pDef->GetItemClass(), "tool" ) || !V_strcmp( pDef->GetItemClass(), "supply_crate" ) );
+ break;
+ }
+
+ case ARMFILT_CLASS_ALL:
+ {
+ bInList = pDef->CanBeUsedByAllClasses();
+ break;
+ }
+
+ case ARMFILT_CLASS_SCOUT:
+ case ARMFILT_CLASS_SNIPER:
+ case ARMFILT_CLASS_SOLDIER:
+ case ARMFILT_CLASS_DEMOMAN:
+ case ARMFILT_CLASS_MEDIC:
+ case ARMFILT_CLASS_HEAVY:
+ case ARMFILT_CLASS_PYRO:
+ case ARMFILT_CLASS_SPY:
+ case ARMFILT_CLASS_ENGINEER:
+ {
+ // Don't show class/slot usage for class/slot tokens
+ if ( pDef->GetItemClass() && !V_strcmp( pDef->GetItemClass(), "class_token" ) )
+ break;
+
+ bInList = ( !pDef->CanBeUsedByAllClasses() && pDef->CanBeUsedByClass( iFilter - ARMFILT_CLASS_SCOUT + 1 ) );
+ break;
+ }
+
+ case ARMFILT_DONATIONITEMS:
+ {
+ // Don't show class/slot usage for class/slot tokens
+ bInList = pDef->GetItemClass() && !V_strcmp( pDef->GetItemClass(), "map_token" );
+ break;
+ }
+ }
+
+ return bInList;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::UpdateItemList( void )
+{
+ int iMaxThumbnails = (m_iThumbnailRows * m_iThumbnailColumns);
+ int iNumThumbnails = MIN( m_FilteredItemList.Count(), iMaxThumbnails );
+
+ if ( m_pThumbnailModelPanels.Count() < iNumThumbnails )
+ {
+ for ( int i = m_pThumbnailModelPanels.Count(); i < iNumThumbnails; i++ )
+ {
+ CItemModelPanel *pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("thumbnailmodelpanel%d", i) ) );
+ pPanel->SetActAsButton( true, true );
+ pPanel->ApplySettings( m_pThumbnailModelPanelKVs );
+ SetBorderForItem( pPanel, false );
+ m_pThumbnailModelPanels.AddToTail( pPanel );
+
+ pPanel->SetTooltip( m_pMouseOverTooltip, "" );
+ }
+ }
+ else if ( m_pThumbnailModelPanels.Count() > iMaxThumbnails )
+ {
+ FOR_EACH_VEC_BACK( m_pThumbnailModelPanels, i )
+ {
+ if ( i < iMaxThumbnails )
+ break;
+
+ m_pThumbnailModelPanels[i]->MarkForDeletion();
+ m_pThumbnailModelPanels.Remove( i );
+ }
+ }
+
+ int iStartPos = (m_iFilterPage * iMaxThumbnails);
+
+ CEconItemView *pItemData = new CEconItemView();
+ FOR_EACH_VEC( m_pThumbnailModelPanels, i )
+ {
+ int iItemPos = iStartPos + i;
+ if ( iItemPos >= m_FilteredItemList.Count() )
+ {
+ m_pThumbnailModelPanels[i]->SetItem( NULL );
+ m_pThumbnailModelPanels[i]->SetVisible( false );
+ continue;
+ }
+
+ pItemData->Init( m_FilteredItemList[iItemPos], AE_USE_SCRIPT_VALUE, AE_USE_SCRIPT_VALUE, true );
+ m_pThumbnailModelPanels[i]->SetItem( pItemData );
+ m_pThumbnailModelPanels[i]->SetVisible( true );
+ }
+ delete pItemData;
+
+ char szTmp[16];
+ int nMaxPages = MAX( 1, ceil(m_FilteredItemList.Count() / (float)(m_iThumbnailRows * m_iThumbnailColumns)) );
+ Q_snprintf(szTmp, 16, "%d/%d", m_iFilterPage+1, nMaxPages );
+ SetDialogVariable( "thumbnailpage", szTmp );
+
+ bool bNextEnabled = m_iFilterPage < (nMaxPages-1);
+ m_pNextPageButton->SetEnabled( bNextEnabled );
+ m_pPrevPageButton->SetEnabled( m_iFilterPage > 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::UpdateSelectedItem( void )
+{
+ if ( !m_SelectedItem.IsValid() )
+ {
+ if ( m_FilteredItemList.Count() )
+ {
+ const CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_FilteredItemList[0] );
+ if ( pDef )
+ {
+ SetSelectedItem( pDef->GetDefinitionIndex() );
+ }
+ }
+ }
+
+ if ( m_pSelectedItemModelPanel )
+ {
+ m_pSelectedItemModelPanel->SetItem( &m_SelectedItem );
+ m_pSelectedItemModelPanel->InvalidateLayout( true );
+
+ int iYDelta = YRES(10);
+
+ // Resize & position the atribute background image
+ int iItemX, iItemY, iItemW, iItemH;
+ m_pSelectedItemModelPanel->GetBounds( iItemX, iItemY, iItemW, iItemH );
+
+ // Never shrink the attribute background below a certain size
+ int iNewPaperH = MAX( iItemH + (iYDelta*2), YRES(100) );
+ int iNewPaperY = iItemY - iYDelta;
+
+ int iNewY = iNewPaperY + iNewPaperH;
+
+ // Reposition the data panel now that we know how big the item is
+ int iX,iY;
+ m_pDataTextRichText->GetPos( iX, iY );
+ int iDataPanelX, iDataPanelY;
+ m_pDataPanel->GetPos( iDataPanelX, iDataPanelY );
+ int iRichTextYPosInDataPanel = iNewY - iDataPanelY;
+ m_pDataTextRichText->SetBounds( iX, iRichTextYPosInDataPanel, m_pDataTextRichText->GetWide(), m_pDataPanel->GetTall() - iRichTextYPosInDataPanel );
+ }
+ if ( m_pSelectedItemImageModelPanel )
+ {
+ m_pSelectedItemImageModelPanel->SetItem( &m_SelectedItem );
+ }
+
+ FOR_EACH_VEC( m_pThumbnailModelPanels, i )
+ {
+ if ( !m_pThumbnailModelPanels[i]->IsVisible() || !m_pThumbnailModelPanels[i]->HasItem() )
+ continue;
+
+ bool bSelected = (m_SelectedItem.GetItemDefIndex() == m_pThumbnailModelPanels[i]->GetItem()->GetItemDefIndex() );
+ if ( bSelected != m_pThumbnailModelPanels[i]->IsSelected() )
+ {
+ m_pThumbnailModelPanels[i]->SetSelected( bSelected );
+ SetBorderForItem( m_pThumbnailModelPanels[i], false );
+ }
+ }
+
+ UpdateDataBlock();
+
+ if ( m_pViewSetButton )
+ {
+ m_pViewSetButton->SetVisible( false );
+ if ( m_SelectedItem.IsValid() )
+ {
+ if ( m_SelectedItem.GetStaticData()->GetItemSetDefinition() )
+ {
+ m_pViewSetButton->SetVisible( true );
+ }
+ }
+ }
+
+ if ( m_pStoreButton )
+ {
+ bool bShowStoreButton = m_bAllowGotoStore && EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( m_SelectedItem.GetItemDefIndex() );
+ m_pStoreButton->SetVisible( bShowStoreButton );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::UpdateDataBlock( void )
+{
+ if ( !CalculateDataText() )
+ {
+ m_pDataPanel->SetVisible( false );
+ return;
+ }
+
+ m_pDataPanel->SetVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CArmoryPanel::CalculateDataText( void )
+{
+ if ( !m_SelectedItem.IsValid() )
+ return false;
+ CTFItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_SelectedItem.GetItemDefIndex() );
+ if ( !pDef )
+ return false;
+
+ m_pDataTextRichText->UpdateDetailsForItem( pDef );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ CEconItemView *pItem = pItemPanel->GetItem();
+ SetBorderForItem( pItemPanel, pItem != NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnItemPanelExited( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ SetBorderForItem( pItemPanel, false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && pItemPanel->HasItem() && !pItemPanel->IsSelected() )
+ {
+ SetSelectedItem( pItemPanel->GetItem() );
+
+ // Hide the mouseover panel now, so it doesn't obscure the rich text info
+ m_pMouseOverItemPanel->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::SetSelectedItem( CEconItemView* newItem )
+{
+ m_PreviousItem = m_SelectedItem;
+ m_SelectedItem = *newItem;
+ m_SelectedItem.SetClientItemFlags( kEconItemFlagClient_Preview );
+ UpdateSelectedItem();
+
+ if ( m_bEventLogging && m_SelectedItem.IsValid() && m_SelectedItem != m_PreviousItem )
+ {
+ C_CTF_GameStats.Event_Catalog( IE_ARMORY_SELECT_ITEM, g_szArmoryFilterStrings[m_CurrentFilter], &m_SelectedItem );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::SetSelectedItem( int newIndex )
+{
+ m_PreviousItem = m_SelectedItem;
+ m_SelectedItem.Init( newIndex, AE_USE_SCRIPT_VALUE, AE_USE_SCRIPT_VALUE, true );
+ m_SelectedItem.SetClientItemFlags( kEconItemFlagClient_Preview );
+
+ if ( m_bEventLogging && m_SelectedItem.IsValid() && m_SelectedItem != m_PreviousItem )
+ {
+ C_CTF_GameStats.Event_Catalog( IE_ARMORY_SELECT_ITEM, g_szArmoryFilterStrings[m_CurrentFilter], &m_SelectedItem );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
+{
+ if ( !pItemPanel )
+ return;
+
+ // Store panels use backgrounds instead of borders
+ pItemPanel->SetBorder( NULL );
+ pItemPanel->SetPaintBackgroundEnabled( true );
+
+ if ( pItemPanel->IsSelected() )
+ {
+ pItemPanel->SetBgColor( m_colThumbnailBGSelected );
+ }
+ else if ( bMouseOver )
+ {
+ pItemPanel->SetBgColor( m_colThumbnailBGMouseover );
+ }
+ else
+ {
+ pItemPanel->SetBgColor( m_colThumbnailBG );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnTextChanged( KeyValues *data )
+{
+ if ( !m_pFilterComboBox )
+ return;
+
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+
+ if ( pComboBox == m_pFilterComboBox )
+ {
+ // the class selection combo box changed, update class details
+ KeyValues *pUserData = m_pFilterComboBox->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ armory_filters_t nFilter = (armory_filters_t)pUserData->GetInt( "setfilter", -1 );
+ if ( nFilter != armory_filters_t(-1) )
+ {
+ SetFilterTo( 0, nFilter );
+ UpdateSelectedItem();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CArmoryPanel::OnItemLinkClicked( KeyValues *pParams )
+{
+ const char *pURL = pParams->GetString( "url" );
+ int iItemDef = atoi( pURL + 7 );
+ JumpToItem( iItemDef, ARMFILT_ALL_ITEMS );
+ m_pFilterComboBox->ActivateItemByRow( 0 );
+}
+
diff --git a/game/client/tf/vgui/charinfo_armory_subpanel.h b/game/client/tf/vgui/charinfo_armory_subpanel.h
new file mode 100644
index 0000000..d27af0b
--- /dev/null
+++ b/game/client/tf/vgui/charinfo_armory_subpanel.h
@@ -0,0 +1,151 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CHARINFO_ARMORY_SUBPANEL_H
+#define CHARINFO_ARMORY_SUBPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/PropertyPage.h"
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ComboBox.h>
+#include "tf_controls.h"
+#include "tf_shareddefs.h"
+#include "backpack_panel.h"
+#include "class_loadout_panel.h"
+
+enum armory_filters_t
+{
+ // These are listed in the dropdown, for players to select
+ ARMFILT_ALL_ITEMS,
+ ARMFILT_WEAPONS,
+ ARMFILT_HEADGEAR,
+ ARMFILT_MISCITEMS,
+ ARMFILT_ACTIONITEMS,
+ ARMFILT_CRAFTITEMS,
+ ARMFILT_TOOLS,
+ ARMFILT_CLASS_ALL,
+ ARMFILT_CLASS_SCOUT,
+ ARMFILT_CLASS_SNIPER,
+ ARMFILT_CLASS_SOLDIER,
+ ARMFILT_CLASS_DEMOMAN,
+ ARMFILT_CLASS_MEDIC,
+ ARMFILT_CLASS_HEAVY,
+ ARMFILT_CLASS_PYRO,
+ ARMFILT_CLASS_SPY,
+ ARMFILT_CLASS_ENGINEER,
+ ARMFILT_DONATIONITEMS,
+
+ ARMFILT_NUM_IN_DROPDOWN,
+
+ ARMFILT_CUSTOM,
+ ARMFILT_TOTAL,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CArmoryPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CArmoryPanel, vgui::EditablePanel );
+public:
+ CArmoryPanel(Panel *parent, const char *panelName);
+ virtual ~CArmoryPanel();
+
+ // Show the armory with one of the default filters set
+ void ShowPanel( int iItemDef, armory_filters_t nFilter = ARMFILT_ALL_ITEMS );
+
+ // Show the armory with a custom list of item definitions, and a custom filter string
+ void ShowPanel( const char *pszFilterString, CUtlVector<item_definition_index_t> *vecItems );
+
+ // Select the given item and jump to the appropriate page
+ void JumpToItem( int iItemDef, armory_filters_t nFilter );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+
+ bool ShouldShowExplanations( void ) { return true; }
+ void UpdateItemList( void );
+ void UpdateSelectedItem( void );
+ void AllowGotoStore( void ) { m_bAllowGotoStore = true; }
+
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+ MESSAGE_FUNC_PARAMS( OnItemLinkClicked, "URLClicked", pParams );
+
+ MESSAGE_FUNC( OnClosing, "Closing" );
+
+private:
+ void OnShowPanel( void );
+ void MoveItem( int iDelta );
+ void UpdateDataBlock( void );
+ bool CalculateDataText( void );
+
+ void SetFilterTo( int iItemDef, armory_filters_t nFilter );
+ void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver );
+ bool DefPassesFilter( const CTFItemDefinition *pDef, armory_filters_t iFilter );
+
+ void SetupComboBox( const char *pszCustomAddition );
+
+ void SetSelectedItem( CEconItemView* newItem );
+ void SetSelectedItem( int newIndex );
+
+private:
+ float m_flStartExplanationsAt;
+
+ CEconItemView m_SelectedItem;
+ CEconItemView m_PreviousItem;
+ CItemModelPanel *m_pSelectedItemModelPanel;
+ CItemModelPanel *m_pSelectedItemImageModelPanel;
+
+ // Filters
+ vgui::ComboBox *m_pFilterComboBox;
+ armory_filters_t m_CurrentFilter;
+ armory_filters_t m_OldFilter;
+ int m_iFilterPage;
+ CExButton *m_pNextPageButton;
+ CExButton *m_pPrevPageButton;
+ CUtlVector<item_definition_index_t> m_FilteredItemList;
+ CUtlVector<item_definition_index_t> m_CustomFilteredList;
+
+ // Thumbnails
+ KeyValues *m_pThumbnailModelPanelKVs;
+ bool m_bReapplyItemKVs;
+ CUtlVector<CItemModelPanel*> m_pThumbnailModelPanels;
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+
+ bool m_bEventLogging; // Handles sending entered/exited stats messages.
+
+ // Data display
+ vgui::EditablePanel *m_pDataPanel;
+ CEconItemDetailsRichText *m_pDataTextRichText;
+ CExButton *m_pViewSetButton;
+
+ bool m_bAllowGotoStore;
+ CExButton *m_pStoreButton;
+
+ CPanelAnimationVar( int, m_iThumbnailRows, "thumbnails_rows", "1" );
+ CPanelAnimationVar( int, m_iThumbnailColumns, "thumbnails_columns", "1" );
+ CPanelAnimationVarAliasType( int, m_iThumbnailX, "thumbnails_x", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iThumbnailY, "thumbnails_y", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iThumbnailDeltaX, "thumbnails_delta_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iThumbnailDeltaY, "thumbnails_delta_y", "0", "proportional_int" );
+ Color m_colThumbnailBG;
+ Color m_colThumbnailBGMouseover;
+ Color m_colThumbnailBGSelected;
+
+ Color m_colSetName;
+};
+
+#endif // CHARINFO_ARMORY_SUBPANEL_H
diff --git a/game/client/tf/vgui/charinfo_loadout_subpanel.cpp b/game/client/tf/vgui/charinfo_loadout_subpanel.cpp
new file mode 100644
index 0000000..42a7989
--- /dev/null
+++ b/game/client/tf/vgui/charinfo_loadout_subpanel.cpp
@@ -0,0 +1,1244 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "charinfo_loadout_subpanel.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "c_tf_freeaccount.h"
+#include "c_tf_player.h"
+#include "confirm_dialog.h"
+#include "gamestringpool.h"
+#include "c_tf_objective_resource.h"
+#include "tf_gamerules.h"
+#include "tf_item_inventory.h"
+#include "trading_start_dialog.h"
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+DECLARE_BUILD_FACTORY( CImageButton );
+
+
+ConVar tf_explanations_charinfopanel( "tf_explanations_charinfopanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CImageButton::CImageButton( vgui::Panel *parent, const char *panelName ) : BaseClass( parent, panelName, "" )
+{
+ m_pszActiveImageName = NULL;
+ m_pszInactiveImageName = NULL;
+
+ m_pActiveImage = NULL;
+ m_pInactiveImage = NULL;
+
+ m_ActiveDrawColor = Color(255,255,255,255);
+ m_InactiveDrawColor = Color(255,255,255,255);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::ApplySettings( KeyValues *inResourceData )
+{
+ m_bScaleImage = inResourceData->GetInt( "scaleImage", 0 );
+
+ // Active Image
+ delete [] m_pszActiveImageName;
+ m_pszActiveImageName = NULL;
+
+ const char *activeImageName = inResourceData->GetString( "activeimage", "" );
+ if ( *activeImageName )
+ {
+ SetActiveImage( activeImageName );
+ }
+
+ // Inactive Image
+ delete [] m_pszInactiveImageName;
+ m_pszInactiveImageName = NULL;
+
+ const char *inactiveImageName = inResourceData->GetString( "inactiveimage", "" );
+ if ( *inactiveImageName )
+ {
+ SetInactiveImage( inactiveImageName );
+ }
+
+ const char *pszDrawColor = inResourceData->GetString("activedrawcolor", "");
+ if (*pszDrawColor)
+ {
+ int r = 0, g = 0, b = 0, a = 255;
+ if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3)
+ {
+ m_ActiveDrawColor = Color(r, g, b, a);
+ }
+ else
+ {
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ m_ActiveDrawColor = pScheme->GetColor(pszDrawColor, Color(0, 0, 0, 0));
+ }
+ }
+
+ pszDrawColor = inResourceData->GetString("inactivedrawcolor", "");
+ if (*pszDrawColor)
+ {
+ int r = 0, g = 0, b = 0, a = 255;
+ if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3)
+ {
+ m_InactiveDrawColor = Color(r, g, b, a);
+ }
+ else
+ {
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ m_InactiveDrawColor = pScheme->GetColor(pszDrawColor, Color(0, 0, 0, 0));
+ }
+ }
+
+ BaseClass::ApplySettings( inResourceData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( m_pszActiveImageName && strlen( m_pszActiveImageName ) > 0 )
+ {
+ SetActiveImage(vgui::scheme()->GetImage( m_pszActiveImageName, m_bScaleImage ) );
+ }
+
+ if ( m_pszInactiveImageName && strlen( m_pszInactiveImageName ) > 0 )
+ {
+ SetInactiveImage(vgui::scheme()->GetImage( m_pszInactiveImageName, m_bScaleImage ) );
+ }
+
+ vgui::IBorder *pBorder = pScheme->GetBorder( "NoBorder" );
+ SetDefaultBorder( pBorder);
+ SetDepressedBorder( pBorder );
+ SetKeyFocusBorder( pBorder );
+
+ Color defaultFgColor = GetSchemeColor( "Button.TextColor", Color(255, 255, 255, 255), pScheme );
+ Color armedFgColor = GetSchemeColor( "Button.ArmedTextColor", Color(255, 255, 255, 255), pScheme );
+ Color depressedFgColor = GetSchemeColor( "Button.DepressedTextColor", Color(255, 255, 255, 255), pScheme );
+
+ Color blank(0,0,0,0);
+ SetDefaultColor( defaultFgColor, blank );
+ SetArmedColor( armedFgColor, blank );
+ SetDepressedColor( depressedFgColor, blank );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::SetActiveImage( const char *imagename )
+{
+ int len = Q_strlen( imagename ) + 1;
+ m_pszActiveImageName = new char[ len ];
+ Q_strncpy( m_pszActiveImageName, imagename, len );
+
+ SetActiveImage(vgui::scheme()->GetImage( m_pszActiveImageName, m_bScaleImage ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::SetInactiveImage( const char *imagename )
+{
+ int len = Q_strlen( imagename ) + 1;
+ m_pszInactiveImageName = new char[ len ];
+ Q_strncpy( m_pszInactiveImageName, imagename, len );
+
+ SetInactiveImage(vgui::scheme()->GetImage( m_pszInactiveImageName, m_bScaleImage ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::SetActiveImage( vgui::IImage *image )
+{
+ m_pActiveImage = image;
+
+ if ( m_pActiveImage )
+ {
+ int wide, tall;
+ if ( m_bScaleImage )
+ {
+ // scaling, force the image size to be our size
+ GetSize( wide, tall );
+ m_pActiveImage->SetSize( wide, tall );
+ }
+ else
+ {
+ // not scaling, so set our size to the image size
+ m_pActiveImage->GetSize( wide, tall );
+ SetSize( wide, tall );
+ }
+ }
+
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::SetInactiveImage( vgui::IImage *image )
+{
+ m_pInactiveImage = image;
+
+ if ( m_pInactiveImage )
+ {
+ int wide, tall;
+ if ( m_bScaleImage)
+ {
+ // scaling, force the image size to be our size
+ GetSize( wide, tall );
+ m_pInactiveImage->SetSize( wide, tall );
+ }
+ else
+ {
+ // not scaling, so set our size to the image size
+ m_pInactiveImage->GetSize( wide, tall );
+ SetSize( wide, tall );
+ }
+ }
+
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::OnSizeChanged( int newWide, int newTall )
+{
+ if ( m_bScaleImage )
+ {
+ // scaling, force the image size to be our size
+ if ( m_pActiveImage )
+ m_pActiveImage->SetSize( newWide, newTall );
+
+ if ( m_pInactiveImage )
+ m_pInactiveImage->SetSize( newWide, newTall );
+ }
+ BaseClass::OnSizeChanged( newWide, newTall );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CImageButton::Paint()
+{
+ if ( IsArmed() || _buttonFlags.IsFlagSet( FORCE_DEPRESSED ) )
+ {
+ // draw the active image
+ if ( m_pActiveImage )
+ {
+ m_pActiveImage->SetColor( m_ActiveDrawColor );
+ m_pActiveImage->SetPos( 0, 0 );
+ m_pActiveImage->Paint();
+ }
+ }
+ else
+ {
+ // draw the inactive image
+ if ( m_pInactiveImage )
+ {
+ m_pActiveImage->SetColor( m_InactiveDrawColor );
+ m_pInactiveImage->SetPos( 0, 0 );
+ m_pInactiveImage->Paint();
+ }
+ }
+
+ BaseClass::Paint();
+}
+
+const char *g_pszSubButtonNames[CHSB_NUM_BUTTONS] =
+{
+ "ShowBackpackButton", // CHSB_BACKPACK,
+ "ShowCraftingButton", // CHSB_CRAFTING,
+ "ShowArmoryButton", // CHSB_ARMORY,
+ "ShowTradeButton", // CHSB_TRADING,
+};
+const char *g_pszSubButtonLabelNames[CHSB_NUM_BUTTONS] =
+{
+ "ShowBackpackLabel", // CHSB_BACKPACK,
+ "ShowCraftingLabel", // CHSB_CRAFTING,
+ "ShowArmoryLabel", // CHSB_ARMORY,
+ "ShowTradeLabel", // CHSB_TRADING,
+};
+
+int g_nLoadoutClassOrder[] =
+{
+ TF_CLASS_SCOUT,
+ TF_CLASS_SOLDIER,
+ TF_CLASS_PYRO,
+ TF_CLASS_DEMOMAN,
+ TF_CLASS_HEAVYWEAPONS,
+ TF_CLASS_ENGINEER,
+ TF_CLASS_MEDIC,
+ TF_CLASS_SNIPER,
+ TF_CLASS_SPY
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCharInfoLoadoutSubPanel::CCharInfoLoadoutSubPanel(Panel *parent) : vgui::PropertyPage(parent, "CharInfoLoadoutSubPanel")
+{
+ m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
+ m_iCurrentTeamIndex = TF_TEAM_RED;
+ m_iShowingPanel = CHAP_LOADOUT;
+ m_iPrevShowingPanel = CHAP_LOADOUT;
+
+ memset( m_pClassButtons, 0, sizeof( m_pClassButtons ) );
+
+ m_pClassButtons[ TF_CLASS_SCOUT ] = new CImageButton( this, "scout" );
+ m_pClassButtons[ TF_CLASS_SOLDIER ] = new CImageButton( this, "soldier" );
+ m_pClassButtons[ TF_CLASS_PYRO ] = new CImageButton( this, "pyro" );
+ m_pClassButtons[ TF_CLASS_DEMOMAN ] = new CImageButton( this, "demoman" );
+ m_pClassButtons[ TF_CLASS_HEAVYWEAPONS ] = new CImageButton( this, "heavyweapons" );
+ m_pClassButtons[ TF_CLASS_ENGINEER ] = new CImageButton( this, "engineer" );
+ m_pClassButtons[ TF_CLASS_MEDIC ] = new CImageButton( this, "medic" );
+ m_pClassButtons[ TF_CLASS_SNIPER ] = new CImageButton( this, "sniper" );
+ m_pClassButtons[ TF_CLASS_SPY ] = new CImageButton( this, "spy" );
+
+ for( int i = 0; i < Q_ARRAYSIZE( m_pClassButtons ); i++ )
+ {
+ if( m_pClassButtons[ i ] )
+ m_pClassButtons[ i ]->SetParentNeedsCursorMoveEvents( true );
+ }
+
+ for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ )
+ {
+ m_pSubButtons[i] = new CImageButton( this, g_pszSubButtonNames[i] );
+ m_pButtonLabels[i] = new CExLabel( this, g_pszSubButtonLabelNames[i], "" );
+ }
+ m_iOverSubButton = -1;
+
+ m_pClassLoadoutPanel = new CClassLoadoutPanel( this );
+ m_pBackpackPanel = new CBackpackPanel( this, "backpack_panel" );
+ m_pCraftingPanel = new CCraftingPanel( this, "crafting_panel" );
+ m_pArmoryPanel = new CArmoryPanel( this, "armory_panel" );
+ m_pArmoryPanel->AllowGotoStore();
+ m_pSelectLabel = NULL;
+ m_pLoadoutChangesLabel = NULL;
+ m_pNoSteamLabel = NULL;
+ m_pNoGCLabel = NULL;
+ m_pClassLabel = NULL;
+ m_pItemsLabel = NULL;
+ m_bSnapClassLayout = false;
+ m_bClassLayoutDirty = false;
+ m_bRequestingInventoryRefresh = false;
+ m_flStartExplanationsAt = 0;
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ REGISTER_COLOR_AS_OVERRIDABLE( m_ItemColorNone, "itemcountcolor_noitems" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_ItemColor, "itemcountcolor" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCharInfoLoadoutSubPanel::~CCharInfoLoadoutSubPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/CharInfoLoadoutSubPanel.res" );
+
+ m_pSelectLabel = dynamic_cast<vgui::Label*>( FindChildByName("SelectLabel") );
+ m_pLoadoutChangesLabel = dynamic_cast<vgui::Label*>( FindChildByName("LoadoutChangesLabel") );
+ m_pNoSteamLabel = dynamic_cast<vgui::Label*>( FindChildByName("NoSteamLabel") );
+ m_pNoGCLabel = dynamic_cast<vgui::Label*>( FindChildByName("NoGCLabel") );
+ m_pClassLabel = dynamic_cast<vgui::Label*>( FindChildByName("ClassLabel") );
+ int ignored;
+ if ( m_pClassLabel )
+ {
+ m_pClassLabel->GetPos( ignored, m_iClassLabelYPos );
+ }
+ m_pItemsLabel = dynamic_cast<CExLabel*>( FindChildByName("ItemsLabel") );
+ if ( m_pItemsLabel )
+ {
+ m_pItemsLabel->GetPos( ignored, m_iItemLabelYPos );
+ }
+
+ // Start classes sized as if the mouse is in the middle of the screen
+ m_iMouseXPos = -1;
+ m_bSnapClassLayout = true;
+ RecalculateTargetClassLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnPageShow( void )
+{
+ SetVisible( true );
+
+ BaseClass::OnPageShow();
+
+ if( m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
+ {
+ m_pClassButtons[ m_iCurrentClassIndex ]->GetPos( m_iMouseXPos, m_iMouseYPos );
+ m_bClassLayoutDirty = true;
+ InvalidateLayout();
+ }
+
+ // If this is the first time we've opened the loadout, start the loadout explanations
+ if ( !tf_explanations_charinfopanel.GetBool() && ShouldShowExplanations() )
+ {
+ m_flStartExplanationsAt = engine->Time() + 0.5;
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnSelectionStarted( void )
+{
+ PostActionSignal( new KeyValues("SelectionUpdate", "open", 1 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnSelectionEnded( void )
+{
+ PostActionSignal( new KeyValues("SelectionUpdate", "open", 0 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnCancelSelection( void )
+{
+ PostMessage( m_pClassLoadoutPanel, new KeyValues("CancelSelection") );
+ PostMessage( m_pBackpackPanel, new KeyValues("CancelSelection") );
+ PostMessage( m_pCraftingPanel, new KeyValues("CancelSelection") );
+ PostMessage( m_pArmoryPanel, new KeyValues("CancelSelection") );
+ RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnCharInfoClosing( void )
+{
+ switch ( m_iShowingPanel )
+ {
+ case CHAP_CRAFTING:
+ PostMessage( m_pCraftingPanel, new KeyValues("Closing") );
+ break;
+ case CHAP_BACKPACK:
+ break;
+ case CHAP_ARMORY:
+ PostMessage( m_pArmoryPanel, new KeyValues("Closing") );
+ break;
+ case CHAP_LOADOUT:
+ PostMessage( m_pClassLoadoutPanel, new KeyValues("Closing") );
+ break;
+ default: // Class loadout.
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnOpenCrafting( void )
+{
+ OpenToCrafting();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnCraftingClosed( void )
+{
+ PostMessage( m_pCraftingPanel, new KeyValues("Closing") );
+ m_iShowingPanel = CHAP_LOADOUT;
+ m_iPrevShowingPanel = CHAP_CRAFTING;
+ m_flStartExplanationsAt = 0;
+ m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
+ UpdateModelPanels();
+ RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnArmoryClosed( void )
+{
+ // Return to whatever we were on before opening the armory
+ PostMessage( m_pArmoryPanel, new KeyValues("Closing") );
+ m_iShowingPanel = m_iPrevShowingPanel;
+ m_iPrevShowingPanel = CHAP_ARMORY;
+ m_flStartExplanationsAt = 0;
+ m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
+ UpdateModelPanels();
+ RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "loadout ", 8 ) )
+ {
+ // Ignore selection while we don't have a steam connection
+ if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() )
+ return;
+
+ m_flStartExplanationsAt = 0;
+
+ const char *pszClass = command+8;
+ if ( pszClass[0] != '\0' )
+ {
+ int nClassIndex = GetClassIndexFromString( pszClass, NUM_CLASSES_IN_LOADOUT_PANEL );
+ if ( nClassIndex != TF_CLASS_UNDEFINED && m_iCurrentClassIndex != nClassIndex )
+ {
+ SetClassIndex( nClassIndex, true );
+ return;
+ }
+ }
+ }
+ else if ( !Q_strnicmp( command, "backpack", 8 ) )
+ {
+ OpenToBackpack();
+ }
+ else if ( !Q_strnicmp( command, "crafting", 8 ) )
+ {
+ OpenToCrafting();
+ }
+ else if ( !Q_strnicmp( command, "armory", 6 ) )
+ {
+ OpenToArmory();
+ }
+ else if ( !Q_strnicmp( command, "trading", 7 ) )
+ {
+ OpenTradingStartDialog( this );
+ }
+ else if ( !Q_stricmp( command, "show_explanations" ) )
+ {
+ if ( !m_flStartExplanationsAt )
+ {
+ m_flStartExplanationsAt = engine->Time();
+ }
+ RequestFocus();
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::RequestInventoryRefresh()
+{
+ m_bRequestingInventoryRefresh = false;
+
+ // Don't respond to the mouse if we don't have items
+ if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() )
+ {
+ ShowWaitingDialog( new CGenericWaitingDialog(this), "#NoSteamNoItems_Refresh", true, true, 30.0f );
+ if ( !m_bRequestingInventoryRefresh )
+ {
+ // make sure the local inventory is added as a listener
+ TFInventoryManager()->UpdateLocalInventory();
+ m_bRequestingInventoryRefresh = true;
+ // ask GC for refresh
+ GCSDK::CProtoBufMsg< CMsgRequestInventoryRefresh > msg( k_EMsgGCRequestInventoryRefresh );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::SetClassIndex( int iClassIndex, bool bOpenClassLoadout )
+{
+ Assert(iClassIndex >= TF_CLASS_UNDEFINED && iClassIndex <= NUM_CLASSES_IN_LOADOUT_PANEL);
+ m_iCurrentClassIndex = iClassIndex;
+ m_iShowingPanel = CHAP_LOADOUT;
+ UpdateModelPanels( bOpenClassLoadout );
+
+ RequestInventoryRefresh();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::SetTeamIndex( int iTeam )
+{
+ Assert( IsValidTFTeam( iTeam ) );
+ m_iCurrentTeamIndex = iTeam;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OpenSubPanel( charinfo_activepanels_t iPanel )
+{
+ m_flStartExplanationsAt = 0;
+ m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
+ m_iPrevShowingPanel = m_iShowingPanel;
+ m_iShowingPanel = iPanel;
+
+ UpdateModelPanels();
+
+ RequestInventoryRefresh();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::UpdateModelPanels( bool bOpenClassLoadout )
+{
+ int iLabelClassToSet = -1;
+ int iClassIndexToSet = 0;
+
+ if ( m_iShowingPanel == CHAP_CRAFTING )
+ {
+ m_pClassLoadoutPanel->SetVisible( false );
+ m_pBackpackPanel->SetVisible( false );
+ m_pArmoryPanel->SetVisible( false );
+ m_pCraftingPanel->ShowPanel( m_iCurrentClassIndex, true, (m_iPrevShowingPanel == CHAP_ARMORY) );
+ }
+ else if ( m_iShowingPanel == CHAP_BACKPACK )
+ {
+ m_pClassLoadoutPanel->SetVisible( false );
+ m_pCraftingPanel->SetVisible( false );
+ m_pArmoryPanel->SetVisible( false );
+ m_pBackpackPanel->ShowPanel( m_iCurrentClassIndex, true, (m_iPrevShowingPanel == CHAP_ARMORY) );
+ }
+ else if ( m_iShowingPanel == CHAP_ARMORY )
+ {
+ m_pClassLoadoutPanel->SetVisible( false );
+ m_pCraftingPanel->SetVisible( false );
+ m_pBackpackPanel->SetVisible( false );
+ m_pArmoryPanel->ShowPanel( m_iArmoryItemDef );
+ }
+ else
+ {
+ iClassIndexToSet = bOpenClassLoadout ? m_iCurrentClassIndex : TF_CLASS_UNDEFINED;
+ m_pArmoryPanel->SetVisible( false );
+ m_pBackpackPanel->SetVisible( false );
+ m_pCraftingPanel->SetVisible( false );
+ m_pClassLoadoutPanel->SetTeam( m_iCurrentTeamIndex );
+ m_pClassLoadoutPanel->SetClass( iClassIndexToSet );
+ m_pClassLoadoutPanel->ShowPanel( iClassIndexToSet, false, (m_iPrevShowingPanel == CHAP_ARMORY) );
+
+ iLabelClassToSet = m_iCurrentClassIndex;
+ }
+
+ m_iCurrentClassIndex = iClassIndexToSet;
+
+ if( bOpenClassLoadout )
+ {
+ PostActionSignal( new KeyValues("ClassSelected", "class", m_iCurrentClassIndex ) );
+ }
+ else
+ {
+ m_iLabelSetToClass = iLabelClassToSet;
+ m_bClassLayoutDirty = true;
+ InvalidateLayout();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // Show our changes label if we're alive, and hence won't get the changes immediately
+ bool bChangesLabel = false;
+ if ( engine->IsInGame() )
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer && pLocalPlayer->IsAlive() && pLocalPlayer->GetObserverMode() == OBS_MODE_NONE )
+ {
+ bChangesLabel = true;
+ }
+ }
+
+ if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() )
+ {
+ bool bLoggedIntoSteam = steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUser()->BLoggedOn();
+ if ( m_pItemsLabel )
+ m_pNoGCLabel->SetVisible( bLoggedIntoSteam );
+ if ( m_pNoSteamLabel )
+ m_pNoSteamLabel->SetVisible( !bLoggedIntoSteam );
+ if ( m_pSelectLabel )
+ m_pSelectLabel->SetVisible( false );
+ if ( m_pLoadoutChangesLabel)
+ m_pLoadoutChangesLabel->SetVisible( false );
+
+ for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ )
+ {
+ m_pSubButtons[i]->SetVisible( false );
+ m_pButtonLabels[i]->SetVisible( false );
+ }
+ }
+ else
+ {
+ if ( m_pNoSteamLabel )
+ m_pNoSteamLabel->SetVisible( false );
+ if ( m_pNoGCLabel )
+ m_pNoGCLabel->SetVisible( false );
+ if ( m_pSelectLabel )
+ m_pSelectLabel->SetVisible( true );
+
+ for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ )
+ {
+ m_pSubButtons[i]->SetVisible( true );
+ m_pButtonLabels[i]->SetVisible( true );
+ }
+ if ( !bChangesLabel )
+ {
+ if ( m_pSelectLabel )
+ m_pSelectLabel->SetPos( 0, m_iSelectLabelY );
+ if ( m_pLoadoutChangesLabel )
+ m_pLoadoutChangesLabel->SetVisible( false );
+ }
+ else
+ {
+ if ( m_pSelectLabel )
+ m_pSelectLabel->SetPos( 0, m_iSelectLabelOnChangesY );
+ if ( m_pLoadoutChangesLabel )
+ m_pLoadoutChangesLabel->SetVisible( true );
+ }
+ }
+
+ m_iOverSubButton = -1;
+ if ( m_pSelectLabel )
+ m_pClassLabel->SetVisible( false );
+ if ( m_pItemsLabel )
+ m_pItemsLabel->SetVisible( false );
+
+ m_bClassLayoutDirty = false;
+
+ // Now Layout the class images.
+ for ( int iPanel = 0; iPanel < ARRAYSIZE( g_nLoadoutClassOrder ); iPanel++ )
+ {
+ int i = g_nLoadoutClassOrder[iPanel];
+
+ int iX = m_iClassLayout[i][0];
+ int iY = m_iClassLayout[i][1];
+ int iWide = m_iClassLayout[i][2];
+ int iTall = m_iClassLayout[i][3];
+
+ if ( m_bSnapClassLayout )
+ {
+ m_pClassButtons[i]->SetBounds( iX, iY, iWide, iTall );
+ }
+ else
+ {
+ // Lerp towards the target
+ int iCurX, iCurY, iCurWide, iCurTall;
+ m_pClassButtons[i]->GetBounds( iCurX, iCurY, iCurWide, iCurTall );
+ int iNewX = Lerp( 0.2, iCurX, iX );
+ int iNewY = Lerp( 0.2, iCurY, iY );
+ int iNewWide = Lerp( 0.2, iCurWide, iWide );
+ int iNewTall = Lerp( 0.2, iCurTall, iTall );
+ m_pClassButtons[i]->SetBounds( iNewX, iNewY, iNewWide, iNewTall );
+ if ( abs(iNewX-iX) > 5 || abs(iNewY-iY) > 5 || abs(iNewWide-iWide) > 5 || abs(iNewTall-iTall) > 5 )
+ {
+ m_bClassLayoutDirty = true;
+ }
+ }
+ }
+
+ // We need to do our own management of cursor arming in the buttons, because the curserentered/exited code can't
+ // deal with the way we resize the buttons without the cursor moving.
+ int iBestButton = -1;
+ int iBestZ = 0;
+ int x = m_iMouseXPos, y = m_iMouseYPos;
+
+ // only get the actual cursor pos if we don't have a cached cursor pos. THe
+ // cached pos might have come from the keyboard.
+ if( x < 0 )
+ vgui::input()->GetCursorPos(x, y);
+ for ( int iPanel = 0; iPanel < ARRAYSIZE( g_nLoadoutClassOrder ); iPanel++ )
+ {
+ int i = g_nLoadoutClassOrder[iPanel];
+ m_pClassButtons[i]->SetArmed( false );
+
+ m_pClassButtons[i]->SetEnabled( TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() );
+
+ if ( m_pClassButtons[i]->IsWithin( x,y ) && iBestZ < m_pClassButtons[i]->GetZPos() )
+ {
+ iBestButton = i;
+ iBestZ = m_pClassButtons[i]->GetZPos();
+ }
+ }
+
+ if ( iBestButton >= 0 && iBestButton < ARRAYSIZE( m_pClassButtons ) )
+ {
+ m_pClassButtons[iBestButton]->SetArmed( true );
+
+ if ( m_iLabelSetToClass != iBestButton )
+ {
+ m_iLabelSetToClass = iBestButton;
+ }
+
+ UpdateLabelFromClass( m_iLabelSetToClass );
+ }
+
+ m_bSnapClassLayout = false;
+}
+
+void CCharInfoLoadoutSubPanel::UpdateLabelFromClass( int nClass )
+{
+ if ( nClass < 0 )
+ return;
+
+ const wchar_t *wszClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[nClass] );
+ if ( m_pClassLabel )
+ {
+ m_pClassLabel->SetText( wszClassName );
+ m_pClassLabel->SetVisible( true );
+ }
+
+ if ( m_pItemsLabel )
+ {
+ m_pItemsLabel->SetVisible( true );
+ }
+
+ CUtlVector<CEconItemView*> pList;
+ int iNumItems = TFInventoryManager()->GetAllUsableItemsForSlot( nClass, -1, &pList );
+
+ if ( !iNumItems )
+ {
+ const wchar_t *wszItemsName = g_pVGuiLocalize->Find( "#NoItemsFoundShort" );
+ m_pItemsLabel->SetText( wszItemsName );
+ m_pItemsLabel->SetColorStr( m_ItemColorNone );
+ }
+ else if ( iNumItems == 1 )
+ {
+ const wchar_t *wszItemsName = g_pVGuiLocalize->Find( "#ItemsFoundShortOne" );
+ m_pItemsLabel->SetText( wszItemsName );
+ m_pItemsLabel->SetColorStr( m_ItemColor );
+ }
+ else
+ {
+ wchar_t wzCount[10];
+ _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iNumItems );
+ wchar_t wTemp[32];
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("ItemsFoundShort"), 1, wzCount );
+ m_pItemsLabel->SetText( wTemp );
+ m_pItemsLabel->SetColorStr( m_ItemColor );
+ }
+
+ int iPos = 0;
+ for ( int i = TF_FIRST_NORMAL_CLASS; i <= NUM_CLASSES_IN_LOADOUT_PANEL; i++ )
+ {
+ if ( iRemapIndexToClass[i] == nClass )
+ {
+ iPos = i;
+ break;
+ }
+ }
+ Assert(iPos != 0 );
+ int iXLeft = (GetWide() - ((m_iClassWideMin * NUM_CLASSES_IN_LOADOUT_PANEL) + (m_iClassXDelta * (NUM_CLASSES_IN_LOADOUT_PANEL-1)))) * 0.5;
+ int iBaseX = iXLeft + ((m_iClassWideMin + m_iClassXDelta) * (iPos-1));
+ int iCenterX = iBaseX + (m_iClassWideMin * 0.5);
+
+ m_pClassLabel->SetVisible( true );
+ m_pClassLabel->SetPos( iCenterX - (m_pClassLabel->GetWide() * 0.5), m_iClassLabelYPos );
+ m_pItemsLabel->SetVisible( true );
+ m_pItemsLabel->SetPos( iCenterX - (m_pItemsLabel->GetWide() * 0.5), m_iItemLabelYPos );
+}
+
+void CCharInfoLoadoutSubPanel::UpdateLabelFromSubButton( int nButton )
+{
+ if( nButton < 0 )
+ nButton = CHSB_NUM_BUTTONS - 1;
+ else if( nButton >= CHSB_NUM_BUTTONS )
+ nButton = 0;
+
+ if ( m_iOverSubButton == nButton )
+ return;
+
+ m_iOverSubButton = nButton;
+
+ switch ( nButton )
+ {
+ default:
+ case CHSB_BACKPACK:
+ {
+ int iNumItems = TFInventoryManager()->GetLocalTFInventory()->GetItemCount();
+ if ( iNumItems == 1 )
+ {
+ const wchar_t *wszItemsName = g_pVGuiLocalize->Find( "#Loadout_OpenBackpackDesc1" );
+ m_pItemsLabel->SetText( wszItemsName );
+ }
+ else
+ {
+ wchar_t wzCount[10];
+ _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iNumItems );
+ wchar_t wTemp[32];
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("Loadout_OpenBackpackDesc"), 1, wzCount );
+ m_pItemsLabel->SetText( wTemp );
+ }
+ }
+ break;
+ case CHSB_CRAFTING:
+ m_pItemsLabel->SetText( g_pVGuiLocalize->Find( "Loadout_OpenCraftingDesc" ) );
+ break;
+ case CHSB_ARMORY:
+ m_pItemsLabel->SetText( g_pVGuiLocalize->Find( "Loadout_OpenArmoryDesc" ) );
+ break;
+ case CHSB_TRADING:
+ m_pItemsLabel->SetText( g_pVGuiLocalize->Find( "Loadout_OpenTradingDesc" ) );
+ break;
+ }
+
+ int iX, iY;
+ m_pSubButtons[nButton]->GetPos( iX, iY );
+ iX += (m_pSubButtons[nButton]->GetWide() * 0.5);
+ iY += m_pSubButtons[nButton]->GetTall() + YRES(5);
+
+ m_pItemsLabel->SetVisible( true );
+ m_pItemsLabel->SetPos( iX - (m_pItemsLabel->GetWide() * 0.5), iY + (m_iItemLabelYPos - m_iClassLabelYPos) );
+ m_pItemsLabel->SetColorStr( m_ItemColor );
+
+ for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ )
+ {
+ m_pSubButtons[i]->SetArmed( false );
+ }
+
+ m_pSubButtons[nButton]->SetArmed( true );
+ m_pSubButtons[nButton]->RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnTick( void )
+{
+ if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
+ return;
+ if ( !IsVisible() )
+ return;
+
+ if ( m_bRequestingInventoryRefresh && TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() )
+ {
+ m_bRequestingInventoryRefresh = false;
+ CloseWaitingDialog();
+ return;
+ }
+
+ // if the class layout is dirty, invalidate our layout so that
+ // we'll animate the class buttons.
+ if ( m_bClassLayoutDirty )
+ {
+ InvalidateLayout();
+ }
+
+ if ( !HasFocus() )
+ return;
+
+ // Don't respond to the mouse if we don't have items
+ if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() )
+ return;
+
+ if ( m_flStartExplanationsAt && m_flStartExplanationsAt < engine->Time() )
+ {
+ m_flStartExplanationsAt = 0;
+
+ if ( ShouldShowExplanations() )
+ {
+ tf_explanations_charinfopanel.SetValue( 1 );
+
+ CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ }
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles mousing over classes
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::OnCursorMoved( int x, int y )
+{
+ RecalculateTargetClassLayoutAtPos( x, y );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles setting the highlighted class for both mouse and keyboard
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::RecalculateTargetClassLayoutAtPos( int x, int y )
+{
+ // Ignore mouse movement outside the buttons
+ bool bWithin = false;
+ for ( int i = TF_FIRST_NORMAL_CLASS; i <= NUM_CLASSES_IN_LOADOUT_PANEL; i++ )
+ {
+ if ( m_pClassButtons[i]->IsWithin(x,y) )
+ {
+ bWithin = true;
+ break;
+ }
+ }
+
+ if ( bWithin )
+ {
+ m_iMouseXPos = x;
+ m_iMouseYPos = y;
+ RecalculateTargetClassLayout();
+ m_bClassLayoutDirty = true;
+ }
+ else
+ {
+ // See if we're over a sub button
+ bool bOverSubButton = false;
+ for ( int i = 0; i < CHSB_NUM_BUTTONS; i++ )
+ {
+ if ( m_pSubButtons[i]->IsWithin(x,y) )
+ {
+ bOverSubButton = true;
+ UpdateLabelFromSubButton( i );
+ }
+ }
+
+ if ( !bOverSubButton && m_pClassLabel->IsVisible() )
+ {
+ // Hide the class label
+ if ( m_iMouseXPos != -1 )
+ {
+ m_iMouseXPos = -1;
+ RecalculateTargetClassLayout();
+ m_bClassLayoutDirty = true;
+ }
+
+ m_iOverSubButton = -1;
+ m_iLabelSetToClass = -1;
+ m_pClassLabel->SetVisible( false );
+ m_pItemsLabel->SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCharInfoLoadoutSubPanel::RecalculateTargetClassLayout( void )
+{
+ // Now Layout the class images.
+ for ( int i = TF_FIRST_NORMAL_CLASS; i <= NUM_CLASSES_IN_LOADOUT_PANEL; i++ )
+ {
+ int iIndex = GetRemappedMenuIndexForClass(i);
+
+ // Figure out where we'd be unscaled
+ int iXLeft = (GetWide() - ((m_iClassWideMin * NUM_CLASSES_IN_LOADOUT_PANEL) + (m_iClassXDelta * (NUM_CLASSES_IN_LOADOUT_PANEL-1)))) * 0.5;
+ int iBaseX = iXLeft + ((m_iClassWideMin + m_iClassXDelta) * (iIndex-1));
+
+ // Scale based on distance from the mouse cursor.
+ int iCenterX = iBaseX + (m_iClassWideMin * 0.5);
+
+ float flScale = 0.0;
+ if ( m_iMouseXPos >= 0 )
+ {
+ flScale = RemapValClamped( abs(m_iMouseXPos - iCenterX), m_iClassDistanceMin, m_iClassDistanceMax, 1.0, 0.0 );
+ }
+
+ float iWide = RemapValClamped( flScale, 0.0, 1.0, m_iClassWideMin, m_iClassWideMax );
+ float iTall = RemapValClamped( flScale, 0.0, 1.0, m_iClassTallMin, m_iClassTallMax );
+
+ int iY = m_iClassYPos - ((iTall - m_iClassTallMin) * 0.5);
+ int iX = iBaseX - ((iWide - m_iClassWideMin) * 0.5);
+
+ m_pClassButtons[i]->SetZPos( flScale * 100 );
+
+ // Cache off the target bounds for this class button
+ m_iClassLayout[i][0] = iX;
+ m_iClassLayout[i][1] = iY;
+ m_iClassLayout[i][2] = iWide;
+ m_iClassLayout[i][3] = iTall;
+ }
+}
+
+void CCharInfoLoadoutSubPanel::MoveCharacterSelection( int nDirection )
+{
+ int nCurrent = 0;
+
+ if ( m_iLabelSetToClass != -1 )
+ {
+ for ( int i = 0; i < ARRAYSIZE( g_nLoadoutClassOrder ); i++ )
+ {
+ if ( m_iLabelSetToClass == g_nLoadoutClassOrder[ i ] )
+ {
+ nCurrent = i;
+ break;
+ }
+ }
+
+ nCurrent += nDirection;
+
+ if ( nCurrent < 0 )
+ {
+ nCurrent = ARRAYSIZE( g_nLoadoutClassOrder ) - 1;
+ }
+ else if ( nCurrent >= ARRAYSIZE( g_nLoadoutClassOrder ) )
+ {
+ nCurrent = 0;
+ }
+ }
+
+ for ( int i = 0; i < ARRAYSIZE( g_nLoadoutClassOrder ); i++ )
+ {
+ m_pClassButtons[ g_nLoadoutClassOrder[ i ] ]->SetArmed( false );
+ }
+
+ // animate the class buttons
+ CImageButton *pButton = m_pClassButtons[ g_nLoadoutClassOrder[ nCurrent ] ];
+ int x, y, wide, tall;
+ pButton->GetBounds( x, y, wide, tall );
+ RecalculateTargetClassLayoutAtPos( x + wide/2, y + tall/2 );
+
+ pButton->RequestFocus();
+}
+
+void CCharInfoLoadoutSubPanel::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ // turn off key handling in this panel when we're showing a loadout
+ // for one class
+ if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
+ {
+ // let escape and B (aka "go back") through so we
+ // can actually get out of the loadout screen
+ if ( code == KEY_ESCAPE )
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+ return;
+ }
+
+ BaseClass::OnKeyCodeTyped( code );
+}
+
+void CCharInfoLoadoutSubPanel::OnKeyCodePressed(vgui::KeyCode code)
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ // turn off key handling in this panel when we're showing a loadout
+ // for one class
+ if( m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
+ {
+ // let escape and B (aka "go back") through so we
+ // can actually get out of the loadout screen
+ if ( nButtonCode == KEY_XBUTTON_B )
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+ return;
+ }
+
+ if ( nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_LEFT ||
+ code == KEY_LEFT )
+ {
+ if ( m_iLabelSetToClass != -1 )
+ {
+ MoveCharacterSelection( -1 );
+ }
+ else
+ {
+ UpdateLabelFromSubButton( m_iOverSubButton - 1 );
+ }
+ return;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_RIGHT ||
+ code == KEY_RIGHT )
+ {
+ if ( m_iLabelSetToClass != -1 )
+ {
+ MoveCharacterSelection( 1 );
+ }
+ else
+ {
+ UpdateLabelFromSubButton( m_iOverSubButton + 1 );
+ }
+ return;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_UP ||
+ nButtonCode == KEY_XSTICK1_UP ||
+ nButtonCode == KEY_XSTICK2_UP ||
+ nButtonCode == STEAMCONTROLLER_DPAD_UP ||
+ code == KEY_UP )
+ {
+ if ( m_iLabelSetToClass == -1 )
+ {
+ m_iLabelSetToClass = g_nLoadoutClassOrder[ 0 ];
+ CImageButton *pButton = m_pClassButtons[ m_iLabelSetToClass ];
+ UpdateLabelFromClass( m_iLabelSetToClass );
+
+ int x, y, wide, tall;
+ pButton->GetBounds( x, y, wide, tall );
+ RecalculateTargetClassLayoutAtPos( x + wide/2, y + tall/2 );
+ pButton->RequestFocus();
+
+ }
+ return;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_DOWN ||
+ nButtonCode == KEY_XSTICK1_DOWN ||
+ nButtonCode == KEY_XSTICK2_DOWN ||
+ nButtonCode == STEAMCONTROLLER_DPAD_DOWN ||
+ code == KEY_DOWN )
+ {
+ if ( m_iLabelSetToClass != -1 )
+ {
+ m_iLabelSetToClass = -1;
+ m_pClassLabel->SetVisible( false );
+ m_pItemsLabel->SetVisible( false );
+
+ for ( int iPanel = 0; iPanel < ARRAYSIZE( g_nLoadoutClassOrder ); iPanel++ )
+ {
+ int i = g_nLoadoutClassOrder[iPanel];
+ m_pClassButtons[i]->SetArmed( false );
+ }
+
+ UpdateLabelFromSubButton( 0 );
+ }
+ return;
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/charinfo_loadout_subpanel.h b/game/client/tf/vgui/charinfo_loadout_subpanel.h
new file mode 100644
index 0000000..ec7581f
--- /dev/null
+++ b/game/client/tf/vgui/charinfo_loadout_subpanel.h
@@ -0,0 +1,177 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CHARINFO_LOADOUT_SUBPANEL_H
+#define CHARINFO_LOADOUT_SUBPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/PropertyPage.h"
+#include <vgui_controls/Button.h>
+#include "tf_controls.h"
+#include "tf_shareddefs.h"
+#include "item_pickup_panel.h"
+#include "backpack_panel.h"
+#include "class_loadout_panel.h"
+#include "crafting_panel.h"
+#include "charinfo_armory_subpanel.h"
+
+#define NUM_CLASSES_IN_LOADOUT_PANEL (TF_LAST_NORMAL_CLASS-1) // We don't allow unlockables for the civilian
+
+class CImageButton : public vgui::Button
+{
+private:
+ DECLARE_CLASS_SIMPLE( CImageButton, vgui::Button );
+
+public:
+ CImageButton( vgui::Panel *parent, const char *panelName );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnSizeChanged( int newWide, int newTall );
+
+ void SetActiveImage( const char *imagename );
+ void SetInactiveImage( const char *imagename );
+ void SetActiveImage( vgui::IImage *image );
+ void SetInactiveImage( vgui::IImage *image );
+
+public:
+ virtual void Paint();
+
+private:
+ vgui::IImage *m_pActiveImage;
+ char *m_pszActiveImageName;
+
+ vgui::IImage *m_pInactiveImage;
+ char *m_pszInactiveImageName;
+
+ bool m_bScaleImage;
+ Color m_ActiveDrawColor;
+ Color m_InactiveDrawColor;
+};
+
+enum charinfo_activepanels_t
+{
+ CHAP_LOADOUT,
+ CHAP_BACKPACK,
+ CHAP_CRAFTING,
+ CHAP_ARMORY,
+};
+
+enum charinfosubbuttons_t
+{
+ CHSB_BACKPACK,
+ CHSB_CRAFTING,
+ CHSB_ARMORY,
+ CHSB_TRADING,
+
+ CHSB_NUM_BUTTONS
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CCharInfoLoadoutSubPanel : public vgui::PropertyPage
+{
+ DECLARE_CLASS_SIMPLE( CCharInfoLoadoutSubPanel, vgui::PropertyPage );
+public:
+ CCharInfoLoadoutSubPanel(Panel *parent);
+ virtual ~CCharInfoLoadoutSubPanel();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void OnCursorMoved( int x, int y );
+
+ void SetClassIndex( int iClassIndex, bool bOpenClassLoadout );
+ void SetTeamIndex( int iTeamIndex );
+ void OpenToBackpack( void ) { OpenSubPanel( CHAP_BACKPACK ); }
+ void OpenToCrafting( void ) { OpenSubPanel( CHAP_CRAFTING ); }
+ void OpenToArmory( int iItemDef = 0 ) { m_iArmoryItemDef = iItemDef; OpenSubPanel( CHAP_ARMORY ); }
+ void OpenSubPanel( charinfo_activepanels_t iPanel );
+ void UpdateModelPanels( bool bOpenClassLoadout = true );
+
+ CClassLoadoutPanel *GetClassLoadoutPanel( void ) { return m_pClassLoadoutPanel; }
+ CBackpackPanel *GetBackpackPanel( void ) { return m_pBackpackPanel; }
+ CCraftingPanel *GetCraftingPanel( void ) { return m_pCraftingPanel; }
+ CArmoryPanel *GetArmoryPanel( void ) { return m_pArmoryPanel; }
+
+ void UpdateLabelFromClass( int nClass );
+ void UpdateLabelFromSubButton( int nButton );
+ virtual void OnTick( void );
+ void RecalculateTargetClassLayout( void );
+ void RecalculateTargetClassLayoutAtPos( int x, int y );
+ void MoveCharacterSelection( int nDirection );
+ void OnKeyCodeTyped( vgui::KeyCode code );
+ void OnKeyCodePressed( vgui::KeyCode code );
+
+ bool ShouldShowExplanations( void ) { return (m_iShowingPanel == CHAP_LOADOUT && m_iCurrentClassIndex == TF_CLASS_UNDEFINED); }
+
+ charinfo_activepanels_t GetShowingPanel() const { return m_iShowingPanel; }
+ int GetCurrentClassIndex() const { return m_iCurrentClassIndex; }
+
+ MESSAGE_FUNC( OnPageShow, "PageShow" );
+ MESSAGE_FUNC( OnSelectionStarted, "SelectionStarted" );
+ MESSAGE_FUNC( OnSelectionEnded, "SelectionEnded" );
+ MESSAGE_FUNC( OnCancelSelection, "CancelSelection" );
+ MESSAGE_FUNC( OnOpenCrafting, "OpenCrafting" );
+ MESSAGE_FUNC( OnCraftingClosed, "CraftingClosed" );
+ MESSAGE_FUNC( OnArmoryClosed, "ArmoryClosed" );
+ MESSAGE_FUNC( OnCharInfoClosing, "CharInfoClosing" );
+
+private:
+ void RequestInventoryRefresh();
+
+ CImageButton *m_pClassButtons[NUM_CLASSES_IN_LOADOUT_PANEL+1];
+ CImageButton *m_pSubButtons[CHSB_NUM_BUTTONS];
+ CExLabel *m_pButtonLabels[CHSB_NUM_BUTTONS];
+ int m_iOverSubButton;
+ int m_iClassLayout[NUM_CLASSES_IN_LOADOUT_PANEL+1][4];
+ bool m_bClassLayoutDirty;
+ bool m_bSnapClassLayout;
+ bool m_bRequestingInventoryRefresh;
+ int m_iCurrentClassIndex;
+ int m_iCurrentTeamIndex;
+ charinfo_activepanels_t m_iShowingPanel;
+ charinfo_activepanels_t m_iPrevShowingPanel;
+ CClassLoadoutPanel *m_pClassLoadoutPanel;
+ CBackpackPanel *m_pBackpackPanel;
+ CCraftingPanel *m_pCraftingPanel;
+ CArmoryPanel *m_pArmoryPanel;
+ vgui::Label *m_pSelectLabel;
+ vgui::Label *m_pLoadoutChangesLabel;
+ vgui::Label *m_pNoSteamLabel;
+ vgui::Label *m_pNoGCLabel;
+ vgui::Label *m_pClassLabel;
+ CExLabel *m_pItemsLabel;
+ int m_iMouseXPos;
+ int m_iMouseYPos;
+ int m_iLabelSetToClass;
+ int m_iClassLabelYPos;
+ int m_iItemLabelYPos;
+ Color m_ItemColorNone;
+ Color m_ItemColor;
+ float m_flStartExplanationsAt;
+ int m_iArmoryItemDef;
+
+ CPanelAnimationVarAliasType( int, m_iSelectLabelY, "selectlabely_default", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iSelectLabelOnChangesY, "selectlabely_onchanges", "0", "proportional_int" );
+
+ CPanelAnimationVarAliasType( int, m_iClassYPos, "class_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iClassXDelta, "class_xdelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iClassWideMin, "class_wide_min", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iClassWideMax, "class_wide_max", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iClassTallMin, "class_tall_min", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iClassTallMax, "class_tall_max", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iClassDistanceMin, "class_distance_min", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iClassDistanceMax, "class_distance_max", "0", "proportional_int" );
+};
+
+#endif // CHARINFO_LOADOUT_SUBPANEL_H
diff --git a/game/client/tf/vgui/class_loadout_panel.cpp b/game/client/tf/vgui/class_loadout_panel.cpp
new file mode 100644
index 0000000..5db5fba
--- /dev/null
+++ b/game/client/tf/vgui/class_loadout_panel.cpp
@@ -0,0 +1,1539 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "class_loadout_panel.h"
+#include "c_tf_player.h"
+#include "vgui_controls/CheckButton.h"
+#include "econ_gcmessages.h"
+#include "gc_clientsystem.h"
+#include "tf_item_system.h"
+#include "loadout_preset_panel.h"
+#include "econ_item_description.h"
+#include "item_style_select_dialog.h"
+#include "vgui/IInput.h"
+#include "vgui_controls/PanelListPanel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern ConVar tf_respawn_on_loadoutchanges;
+
+ConVar tf_show_preset_explanation_in_class_loadout( "tf_show_preset_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
+ConVar tf_show_taunt_explanation_in_class_loadout( "tf_show_taunt_explanation_in_class_loadout", "1", FCVAR_HIDDEN | FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
+
+void ParticleSlider_UpdateRequest( int iLoadoutPosition, float value )
+{
+ CClassLoadoutPanel *pPanel = g_pClassLoadoutPanel;
+ if ( !pPanel )
+ return;
+
+ CEconItemView *pHat = pPanel->GetItemInSlot( iLoadoutPosition );
+ if ( !pHat )
+ return;
+
+ // does this hat even have a particle effect
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ uint32 iHasEffect = 0;
+ if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) )
+ return;
+
+ // Check for use head toggle
+ static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" );
+ uint32 iUseHead = 0;
+ if ( !pHat->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) || iUseHead == 0 )
+ return;
+
+ // Look for the attribute and request to change it
+ static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
+ uint32 iOffSet = 0;
+ if ( !pHat->FindAttribute( pAttrDef_VerticalOffset, &iOffSet ) && value == 0 )
+ {
+ return;
+ }
+ else
+ {
+ const float& flAttrValue = (float&)iOffSet;
+ if ( value == flAttrValue )
+ return; // no change do nothing
+ }
+
+ // Send a message to the GC to request a change
+ GCSDK::CProtoBufMsg<CMsgSetItemEffectVerticalOffset> msg( k_EMsgGCSetItemEffectVerticalOffset );
+ msg.Body().set_item_id( pHat->GetItemID() );
+ msg.Body().set_offset( value );
+ GCClientSystem()->BSendMessage( msg );
+}
+
+void HatOffset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
+{
+ ConVarRef cVarRef( pConVar );
+ ParticleSlider_UpdateRequest( LOADOUT_POSITION_HEAD, cVarRef.GetFloat() );
+}
+ConVar tf_hat_effect_offset( "tf_hat_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", HatOffset_Callback );
+
+void Misc1Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
+{
+ ConVarRef cVarRef( pConVar );
+ ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC, cVarRef.GetFloat() );
+}
+ConVar tf_misc1_effect_offset( "tf_misc1_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc1Offset_Callback );
+
+void Misc2Offset_Callback( IConVar *pConVar, char const *pOldString, float flOldValue )
+{
+ ConVarRef cVarRef( pConVar );
+ ParticleSlider_UpdateRequest( LOADOUT_POSITION_MISC2, cVarRef.GetFloat() );
+}
+ConVar tf_misc2_effect_offset( "tf_misc2_effect_offset", "0", FCVAR_DEVELOPMENTONLY, "Adjust the position of the unusual effect for your hat.", Misc2Offset_Callback );
+
+
+// Hacky solution to different classes wanting different slots visible in their loadouts, and in different positions
+struct LoadoutPanelPositioningInstance
+{
+ int m_iPos[NUM_ITEM_PANELS_IN_LOADOUT];
+};
+
+bool IsTauntPanelPosition( int iButtonPos )
+{
+ return iButtonPos >= 9 && iButtonPos <= 16;
+}
+
+const LoadoutPanelPositioningInstance g_DefaultLoadoutPanelPositioning =
+{
+ {
+ 1, // LOADOUT_POSITION_PRIMARY = 0,
+ 2, // LOADOUT_POSITION_SECONDARY,
+ 3, // LOADOUT_POSITION_MELEE,
+ 0, // LOADOUT_POSITION_UTILITY, // STAGING ONLY
+ 0, // LOADOUT_POSITION_BUILDING,
+ 0, // LOADOUT_POSITION_PDA,
+ 0, // LOADOUT_POSITION_PDA2,
+ 5, // LOADOUT_POSITION_HEAD,
+ 6, // LOADOUT_POSITION_MISC,
+ 8, // LOADOUT_POSITION_ACTION,
+ 7, // LOADOUT_POSITION_MISC2,
+ 9, // LOADOUT_POSITION_TAUNT,
+ 10, // LOADOUT_POSITION_TAUNT2,
+ 11, // LOADOUT_POSITION_TAUNT3,
+ 12, // LOADOUT_POSITION_TAUNT4,
+ 13, // LOADOUT_POSITION_TAUNT5,
+ 14, // LOADOUT_POSITION_TAUNT6,
+ 15, // LOADOUT_POSITION_TAUNT7,
+ 16, // LOADOUT_POSITION_TAUNT8,
+
+#ifdef STAGING_ONLY
+ 0, // LOADOUT_POSITION_PDA_ADDON1,
+ 0, // LOADOUT_POSITION_PDA_ADDON2,
+ 0, // LOADOUT_POSITION_PDA3,
+ //9, // LOADOUT_POSITION_MISC3,
+ //10, // LOADOUT_POSITION_MISC4,
+ //11, // LOADOUT_POSITION_MISC5,
+ //12, // LOADOUT_POSITION_MISC6,
+ //13, // LOADOUT_POSITION_MISC7,
+ //14, // LOADOUT_POSITION_MISC8,
+ //15, // LOADOUT_POSITION_MISC9,
+ //16, // LOADOUT_POSITION_MISC10,
+ 0, // LOADOUT_POSITION_BUILDING2,
+#endif // STAGING_ONLY
+ }
+};
+
+const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Spy =
+{
+ {
+ 0, // LOADOUT_POSITION_PRIMARY = 0,
+ 1, // LOADOUT_POSITION_SECONDARY,
+ 2, // LOADOUT_POSITION_MELEE,
+ 0, // LOADOUT_POSITION_UTILITY, // STAGING ONLY
+ 4, // LOADOUT_POSITION_BUILDING, // sapper
+ 0, // LOADOUT_POSITION_PDA, // disguise kit (Hidden)
+ 3, // LOADOUT_POSITION_PDA2, // Watch
+ 5, // LOADOUT_POSITION_HEAD,
+ 6, // LOADOUT_POSITION_MISC,
+ 8, // LOADOUT_POSITION_ACTION,
+ 7, // LOADOUT_POSITION_MISC2,
+ 9, // LOADOUT_POSITION_TAUNT,
+ 10, // LOADOUT_POSITION_TAUNT2,
+ 11, // LOADOUT_POSITION_TAUNT3,
+ 12, // LOADOUT_POSITION_TAUNT4,
+ 13, // LOADOUT_POSITION_TAUNT5,
+ 14, // LOADOUT_POSITION_TAUNT6,
+ 15, // LOADOUT_POSITION_TAUNT7,
+ 16, // LOADOUT_POSITION_TAUNT8,
+#ifdef STAGING_ONLY
+ 0, // LOADOUT_POSITION_PDA_ADDON1,
+ 0, // LOADOUT_POSITION_PDA_ADDON2,
+ 0, // LOADOUT_POSITION_PDA3,
+ //9, // LOADOUT_POSITION_MISC3,
+ //10, // LOADOUT_POSITION_MISC4,
+ //11, // LOADOUT_POSITION_MISC5,
+ //12, // LOADOUT_POSITION_MISC6,
+ //13, // LOADOUT_POSITION_MISC7,
+ //14, // LOADOUT_POSITION_MISC8,
+ //15, // LOADOUT_POSITION_MISC9,
+ //16, // LOADOUT_POSITION_MISC10,
+ 0, // LOADOUT_POSITION_BUILDING2,
+#endif // STAGING_ONLY
+ }
+};
+
+const LoadoutPanelPositioningInstance g_LoadoutPanelPositioning_Engineer =
+{
+ {
+ 1, // LOADOUT_POSITION_PRIMARY = 0,
+ 2, // LOADOUT_POSITION_SECONDARY,
+ 3, // LOADOUT_POSITION_MELEE,
+ 0, // LOADOUT_POSITION_UTILITY, // STAGING ONLY
+ 0, // LOADOUT_POSITION_BUILDING,
+ 4, // LOADOUT_POSITION_PDA,
+ 0, // LOADOUT_POSITION_PDA2,
+ 5, // LOADOUT_POSITION_HEAD,
+ 6, // LOADOUT_POSITION_MISC,
+ 8, // LOADOUT_POSITION_ACTION,
+ 7, // LOADOUT_POSITION_MISC2,
+ 9, // LOADOUT_POSITION_TAUNT,
+ 10, // LOADOUT_POSITION_TAUNT2,
+ 11, // LOADOUT_POSITION_TAUNT3,
+ 12, // LOADOUT_POSITION_TAUNT4,
+ 13, // LOADOUT_POSITION_TAUNT5,
+ 14, // LOADOUT_POSITION_TAUNT6,
+ 15, // LOADOUT_POSITION_TAUNT7,
+ 16, // LOADOUT_POSITION_TAUNT8,
+#ifdef STAGING_ONLY
+ 17, // LOADOUT_POSITION_PDA_ADDON1,
+ 18, // LOADOUT_POSITION_PDA_ADDON2,
+ 0, // LOADOUT_POSITION_PDA3,
+ //9, // LOADOUT_POSITION_MISC3,
+ //10, // LOADOUT_POSITION_MISC4,
+ //11, // LOADOUT_POSITION_MISC5,
+ //12, // LOADOUT_POSITION_MISC6,
+ //13, // LOADOUT_POSITION_MISC7,
+ //14, // LOADOUT_POSITION_MISC8,
+ //15, // LOADOUT_POSITION_MISC9,
+ //16, // LOADOUT_POSITION_MISC10,
+ 0, // LOADOUT_POSITION_BUILDING2,
+#endif // STAGING_ONLY
+ }
+};
+
+const LoadoutPanelPositioningInstance *g_VisibleLoadoutSlotsPerClass[] =
+{
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_UNDEFINED
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_SCOUT
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_SNIPER
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_SOLDIER
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_DEMOMAN
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_MEDIC
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_HEAVYWEAPONS
+ &g_DefaultLoadoutPanelPositioning, // TF_CLASS_PYRO
+ &g_LoadoutPanelPositioning_Spy, // TF_CLASS_SPY
+ &g_LoadoutPanelPositioning_Engineer, // TF_CLASS_ENGINEER
+};
+
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_VisibleLoadoutSlotsPerClass ) == TF_LAST_NORMAL_CLASS );
+
+//-----------------------------------------------------------------------------
+// Particle Effect Slider
+//-----------------------------------------------------------------------------
+CLoadoutItemOptionsPanel::CLoadoutItemOptionsPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName )
+{
+ m_pHatParticleSlider = NULL;
+ m_pHatParticleUseHeadButton = NULL;
+
+ m_iCurrentClassIndex = -1;
+ m_eItemSlot = LOADOUT_POSITION_INVALID;
+
+ m_pListPanel = new vgui::PanelListPanel( this, "PanelListPanel" );
+ m_pListPanel->SetFirstColumnWidth( 0 );
+ m_pHatParticleSlider = new CCvarSlider( m_pListPanel, "HatParticleSlider" );
+ m_pHatParticleSlider->AddActionSignalTarget( this );
+ m_pHatParticleUseHeadButton = new vgui::CheckButton( m_pListPanel, "HatUseHeadCheckButton", "#GameUI_ParticleHatUseHead" );
+ m_pHatParticleUseHeadButton->AddActionSignalTarget( this );
+ m_pSetStyleButton = new CExButton( m_pListPanel, "SetStyleButton", "#TF_Item_SelectStyle" );
+ m_pSetStyleButton->AddActionSignalTarget( this );
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ LoadControlSettings( "resource/UI/ItemOptionsPanel.res" );
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+ m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "particle_button_clicked" ) )
+ {
+ UpdateItemOptionsUI();
+ return;
+ }
+ else if ( FStrEq( command, "particle_use_head_clicked" ) )
+ {
+ // Grab current hat
+ CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
+ if ( !pHat )
+ return;
+
+ // does this hat even have a particle effect
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ uint32 iHasEffect = 0;
+ if ( !pHat->FindAttribute( pAttrDef_AttachParticleEffect, &iHasEffect ) )
+ return;
+
+ // Send a message to the GC to request a change
+ GCSDK::CProtoBufMsg<CMsgSetHatEffectUseHeadOrigin> msg( k_EMsgGCSetHatEffectUseHeadOrigin );
+ msg.Body().set_item_id( pHat->GetItemID() );
+ msg.Body().set_use_head( m_pHatParticleUseHeadButton->IsSelected() );
+ GCClientSystem()->BSendMessage( msg );
+ return;
+ }
+ else if ( FStrEq( command, "set_style" ) )
+ {
+ CEconItemView *pHat = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
+ CStyleSelectDialog *pStyle = vgui::SETUP_PANEL( new CStyleSelectDialog( GetParent(), pHat ) );
+ if ( pStyle )
+ {
+ pStyle->Show();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel )
+{
+ if ( FStrEq( pParams->GetName(), "SliderDragEnd" ) )
+ {
+ m_pHatParticleSlider->ApplyChanges();
+ }
+
+ BaseClass::OnMessage( pParams, hFromPanel );
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::SetItemSlot( loadout_positions_t eItemSlot, int iClassIndex )
+{
+ m_eItemSlot = eItemSlot;
+ m_iCurrentClassIndex = iClassIndex;
+ // Init the Slider based on the slot
+ const char * pszConVarName = NULL;
+
+ switch ( eItemSlot )
+ {
+ case LOADOUT_POSITION_HEAD :
+ pszConVarName = "tf_hat_effect_offset";
+ break;
+ case LOADOUT_POSITION_MISC :
+ pszConVarName = "tf_misc1_effect_offset";
+ break;
+ case LOADOUT_POSITION_MISC2 :
+ pszConVarName = "tf_misc2_effect_offset";
+ break;
+ default:
+ break;
+ }
+
+ if ( pszConVarName )
+ {
+ m_pHatParticleSlider->SetupSlider( -8, 8, pszConVarName, false );
+ }
+
+ m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
+ m_pHatParticleSlider->SetTickCaptions( "", "" );
+
+ UpdateItemOptionsUI();
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::UpdateItemOptionsUI()
+{
+ if ( m_eItemSlot == LOADOUT_POSITION_INVALID )
+ return;
+
+ m_pListPanel->RemoveAll();
+
+ // Add controls for various item options
+ AddControlsParticleEffect();
+ AddControlsSetStyle();
+
+ // Bail if no controls added
+ if ( m_pListPanel->GetItemCount() == 0 )
+ {
+ // We should have some controls if we get to this point.
+ Assert( 0 );
+ SetVisible( false );
+ return;
+ }
+
+ // Resize the background and list panel to contain all the controls
+ int nVertPixels = m_pListPanel->ComputeVPixelsNeeded();
+ int nNewTall = Min( 200, nVertPixels );
+ m_pListPanel->SetTall( nNewTall );
+ SetTall( nNewTall );
+ InvalidateLayout( true, false );
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::AddControlsParticleEffect( void ) const
+{
+ m_pHatParticleUseHeadButton->SetVisible( false );
+ m_pHatParticleSlider->SetVisible( false );
+
+ CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
+ if ( pItem )
+ {
+ // does this hat even have a particle effect
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ uint32 iValue = 0;
+ if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) )
+ {
+ m_pHatParticleUseHeadButton->SetVisible( true );
+ m_pListPanel->AddItem( NULL, m_pHatParticleUseHeadButton );
+ m_pListPanel->AddItem( NULL, m_pHatParticleSlider );
+
+ // Check for use head toggle
+ static CSchemaAttributeDefHandle pAttrDef_UseHeadOrigin( "particle effect use head origin" );
+ uint32 iUseHead = 0;
+ if ( pItem->FindAttribute( pAttrDef_UseHeadOrigin, &iUseHead ) && iUseHead > 0 )
+ {
+ m_pHatParticleSlider->SetVisible( true );
+
+ m_pHatParticleUseHeadButton->SetSelected( true );
+ m_pHatParticleSlider->SetTickColor( Color( 235, 226, 202, 255 ) ); // tanlight
+ m_pHatParticleSlider->Repaint();
+
+ // Get offset if it exists
+ static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
+ uint32 iOffset = 0;
+ if ( pItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) )
+ {
+ m_pHatParticleSlider->SetSliderValue( (float&)iOffset );
+ }
+ }
+ else
+ {
+ m_pHatParticleUseHeadButton->SetSelected( false );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CLoadoutItemOptionsPanel::AddControlsSetStyle( void ) const
+{
+ m_pSetStyleButton->SetVisible( false );
+
+ CEconItemView *pItem = GetItem();
+ if ( pItem && pItem->GetStaticData()->GetNumStyles() )
+ {
+ m_pSetStyleButton->SetVisible( true );
+ m_pListPanel->AddItem( NULL, m_pSetStyleButton );
+ }
+}
+
+//-----------------------------------------------------------------------------
+CEconItemView* CLoadoutItemOptionsPanel::GetItem( void ) const
+{
+ return TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, m_eItemSlot );
+}
+
+CClassLoadoutPanel *g_pClassLoadoutPanel = NULL;
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CClassLoadoutPanel::CClassLoadoutPanel( vgui::Panel *parent )
+ : CBaseLoadoutPanel( parent, "class_loadout_panel" )
+ , m_pItemOptionPanelKVs( NULL )
+{
+ m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
+ m_iCurrentTeamIndex = TF_TEAM_RED;
+ m_iCurrentSlotIndex = -1;
+ m_pPlayerModelPanel = NULL;
+ m_pSelectionPanel = NULL;
+ m_pTauntHintLabel = NULL;
+ m_pTauntLabel = NULL;
+ m_pTauntCaratLabel = NULL;
+ m_pPassiveAttribsLabel = NULL;
+ m_pLoadoutPresetPanel = NULL;
+ m_pPresetsExplanationPopup = NULL;
+ m_pTauntsExplanationPopup = NULL;
+ m_pBuildablesButton = NULL;
+
+ m_pCharacterLoadoutButton = NULL;
+ m_pTauntLoadoutButton = NULL;
+
+ m_bInTauntLoadoutMode = false;
+
+ g_pClassLoadoutPanel = this;
+
+ m_pItemOptionPanel = new CLoadoutItemOptionsPanel( this, "ItemOptionsPanel" );
+}
+
+CClassLoadoutPanel::~CClassLoadoutPanel()
+{
+ if ( m_pItemOptionPanelKVs )
+ {
+ m_pItemOptionPanelKVs->deleteThis();
+ m_pItemOptionPanelKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/ClassLoadoutPanel.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("classmodelpanel") );
+ m_pTauntHintLabel = dynamic_cast<vgui::Label*>( FindChildByName("TauntHintLabel") );
+ m_pTauntLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntLabel") );
+ m_pTauntCaratLabel = dynamic_cast<CExLabel*>( FindChildByName("TauntCaratLabel") );
+ m_pBuildablesButton = dynamic_cast<CExButton*>( FindChildByName("BuildablesButton") );
+ m_pCharacterLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("CharacterLoadoutButton") );
+ m_pTauntLoadoutButton = dynamic_cast<CExImageButton*>( FindChildByName("TauntLoadoutButton") );
+ m_pPassiveAttribsLabel = dynamic_cast<CExLabel*>( FindChildByName("PassiveAttribsLabel") );
+ m_pLoadoutPresetPanel = dynamic_cast<CLoadoutPresetPanel*>( FindChildByName( "loadout_preset_panel" ) );
+ m_pPresetsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "PresetsExplanation" ) );
+ m_pTauntsExplanationPopup = dynamic_cast<CExplanationPopup*>( FindChildByName( "TauntsExplanation" ) );
+ m_pTopLinePanel = FindChildByName( "TopLine" );
+ if ( m_pPassiveAttribsLabel )
+ {
+ m_pPassiveAttribsLabel->SetMouseInputEnabled( false );
+ }
+
+ m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE );
+
+ m_aDefaultColors[LOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorFg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorFg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorFg", Color( 255, 255, 255, 255 ) );
+
+ m_aDefaultColors[LOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorBg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorBg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorBg", Color( 255, 255, 255, 255 ) );
+
+ m_aDefaultColors[NOTLOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.TextColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedTextColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedTextColor", Color( 255, 255, 255, 255 ) );
+
+ m_aDefaultColors[NOTLOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.BgColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedBgColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedBgColor", Color( 255, 255, 255, 255 ) );
+}
+
+
+void CClassLoadoutPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "itemoptionpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemOptionPanelKVs )
+ {
+ m_pItemOptionPanelKVs->deleteThis();
+ }
+ m_pItemOptionPanelKVs = new KeyValues("itemoptionpanels_kv");
+ pItemKV->CopySubkeys( m_pItemOptionPanelKVs );
+ }
+
+#ifdef STAGING_ONLY
+ // PDA Panels
+ if ( m_pItemModelPanels.Count() > LOADOUT_POSITION_PDA_ADDON2 )
+ {
+ for ( int i = LOADOUT_POSITION_PDA_ADDON1; i <= LOADOUT_POSITION_PDA_ADDON2; i++ )
+ {
+ int wide = m_pItemModelPanels[i]->GetWide();
+ int tall = m_pItemModelPanels[i]->GetTall();
+
+ m_pItemModelPanels[i]->SetSize( wide / 2, tall / 2 );
+ m_pItemModelPanels[i]->InvalidateLayout();
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // This is disabled by default in res file. IF we turn it on again, uncomment this.
+ /*if ( m_pPassiveAttribsLabel )
+ {
+ m_pPassiveAttribsLabel->SetVisible( !m_bInTauntLoadoutMode );
+ }*/
+
+ if ( m_pTauntHintLabel )
+ {
+ m_pTauntHintLabel->SetVisible( m_bInTauntLoadoutMode );
+
+ const char *key = engine->Key_LookupBinding( "taunt" );
+ if ( !key )
+ {
+ key = "< not bound >";
+ }
+ SetDialogVariable( "taunt", key );
+ }
+
+ if ( m_pTauntLabel )
+ {
+ m_pTauntLabel->SetVisible( m_bInTauntLoadoutMode );
+ }
+ if ( m_pTauntCaratLabel )
+ {
+ m_pTauntCaratLabel->SetVisible( m_bInTauntLoadoutMode );
+ }
+ if ( m_pCharacterLoadoutButton )
+ {
+ UpdatePageButtonColor( m_pCharacterLoadoutButton, !m_bInTauntLoadoutMode );
+ }
+ if ( m_pTauntLoadoutButton )
+ {
+ UpdatePageButtonColor( m_pTauntLoadoutButton, m_bInTauntLoadoutMode );
+ }
+
+ FOR_EACH_VEC( m_vecItemOptionButtons, i )
+ {
+ m_vecItemOptionButtons[i]->SetVisible( false );
+ }
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ // Viewing a class loadout. Layout the buttons & the class image.
+ if ( i >= NUM_ITEM_PANELS_IN_LOADOUT )
+ {
+ m_pItemModelPanels[i]->SetVisible( false );
+ continue;
+ }
+
+ int iButtonPos = 0;
+ if ( m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
+ {
+ iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[i];
+ }
+
+ bool bIsVisible = false;
+ if ( iButtonPos > 0 )
+ {
+ bIsVisible = m_bInTauntLoadoutMode ? IsTauntPanelPosition( iButtonPos ) : !IsTauntPanelPosition( iButtonPos );
+ }
+ m_pItemModelPanels[i]->SetVisible( bIsVisible );
+
+ if ( bIsVisible )
+ {
+ if ( m_bInTauntLoadoutMode )
+ {
+ iButtonPos -= g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[LOADOUT_POSITION_TAUNT];
+ }
+ else
+ {
+ iButtonPos--;
+ }
+
+#ifdef STAGING_ONLY
+ // Override for the PDA AddOnSlots
+ if ( i == LOADOUT_POSITION_PDA_ADDON1 )
+ {
+ int iYPos, iXPos;
+ m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetPos( iXPos, iYPos );
+ int iWide, iTall;
+ m_pItemModelPanels[ LOADOUT_POSITION_PDA ]->GetSize( iWide, iTall );
+
+ m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos );
+ m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 );
+ continue;
+ }
+ else if ( i == LOADOUT_POSITION_PDA_ADDON2 )
+ {
+ int iYPos, iXPos;
+ m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetPos( iXPos, iYPos );
+ int iWide, iTall;
+ m_pItemModelPanels[LOADOUT_POSITION_PDA]->GetSize( iWide, iTall );
+
+ m_pItemModelPanels[i]->SetPos( iXPos + iWide + XRES(1), iYPos + iTall - (iTall / 2.1 ) );
+ m_pItemModelPanels[i]->SetSize( iWide / 2.1, iTall / 2.1 );
+ continue;
+ }
+#endif
+
+ m_pItemModelPanels[i]->SetNoItemText( ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[i] );
+
+ int iCenter = GetWide() * 0.5;
+ int iColumnHeight = 4;
+ int iColumn = iButtonPos / iColumnHeight;
+ int iYButtonPos = iButtonPos % iColumnHeight;
+
+ int iOffset = iColumn == 0 ? m_iItemXPosOffcenterA : m_iItemXPosOffcenterB + ((iColumn - 1) * 200);
+ int iXPos = iCenter + iOffset;
+ int iYPos = m_iItemYPos + (m_iItemYDelta * iYButtonPos);
+ m_pItemModelPanels[i]->SetPos( iXPos, iYPos );
+
+ // Update position and visibility of the item option buttons
+ if ( i < m_vecItemOptionButtons.Count() )
+ {
+ // Place the button just inside the item model panel
+ CExButton* pItemOptionsPanel = m_vecItemOptionButtons[iButtonPos];
+ int iButtonWide = m_pItemModelPanels[i]->GetWide();
+ int iMyWide = pItemOptionsPanel->GetWide();
+ int iOptionsXPos = iColumn == 0
+ ? iXPos + iButtonWide - iMyWide
+ : iXPos;
+ pItemOptionsPanel->SetPos( iOptionsXPos, iYPos );
+
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
+ // Enable or disable the item options button for this item model panel
+ pItemOptionsPanel->SetVisible( !m_bInTauntLoadoutMode && AnyOptionsAvailableForItem( pItemData ) );
+ pItemOptionsPanel->SetCommand( CFmtStr( "options%d", i ) );
+ }
+ }
+
+ m_pItemModelPanels[ i ]->SetSelected( false );
+ }
+
+ if ( m_pLoadoutPresetPanel )
+ {
+ m_pLoadoutPresetPanel->SetPos( ( ScreenWidth() - m_pLoadoutPresetPanel->GetWide() ) / 2, m_iItemYPos );
+ }
+
+ LinkModelPanelControllerNavigation( true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnKeyCodePressed( vgui::KeyCode code )
+{
+ // See if the preset control uses this key
+ if( m_pLoadoutPresetPanel->HandlePresetKeyPressed( code ) )
+ {
+ return;
+ }
+
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if (nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_LEFT ||
+ code == KEY_LEFT ||
+ nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_RIGHT ||
+ code == KEY_RIGHT ||
+ nButtonCode == KEY_XBUTTON_UP ||
+ nButtonCode == KEY_XSTICK1_UP ||
+ nButtonCode == KEY_XSTICK2_UP ||
+ nButtonCode == STEAMCONTROLLER_DPAD_UP ||
+ code == KEY_UP ||
+ nButtonCode == KEY_XBUTTON_DOWN ||
+ nButtonCode == KEY_XSTICK1_DOWN ||
+ nButtonCode == KEY_XSTICK2_DOWN ||
+ nButtonCode == STEAMCONTROLLER_DPAD_DOWN ||
+ code == KEY_DOWN )
+ {
+ // just eat all navigation keys so we don't
+ // end up with undesirable navigation behavior bubbling from
+ // one item model panel to another
+ }
+ else if( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A )
+ {
+ // show the current loadout slot
+ int nSelected = GetFirstSelectedItemIndex( true );
+ if( nSelected != -1 )
+ {
+ OnCommand( VarArgs("change%d", nSelected ) );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnNavigateTo( const char* panelName )
+{
+ CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) );
+ if( !pChild )
+ return;
+
+ pChild->SetSelected( true );
+ SetBorderForItem( pChild, false );
+ pChild->RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnNavigateFrom( const char* panelName )
+{
+ CItemModelPanel *pChild = dynamic_cast<CItemModelPanel *>( FindChildByName( panelName ) );
+ if( !pChild )
+ return;
+
+ pChild->SetSelected( false );
+ SetBorderForItem( pChild, false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
+{
+ if ( bVisible )
+ {
+ // always start in character loadout page
+ SetLoadoutPage( CHARACTER_LOADOUT_PAGE );
+
+ if ( m_pSelectionPanel )
+ {
+ m_pSelectionPanel->SetVisible( false );
+ m_pSelectionPanel->MarkForDeletion();
+ m_pSelectionPanel = NULL;
+ }
+
+ m_iCurrentSlotIndex = TF_WPN_TYPE_PRIMARY;
+ if( m_pItemModelPanels.Count() && m_pItemModelPanels[0] )
+ {
+ m_pItemModelPanels[0]->SetSelected( true );
+ SetBorderForItem( m_pItemModelPanels[0], false );
+ }
+
+ m_bLoadoutHasChanged = false;
+
+ if ( tf_show_preset_explanation_in_class_loadout.GetBool() && m_pPresetsExplanationPopup )
+ {
+ m_pPresetsExplanationPopup->Popup();
+ tf_show_preset_explanation_in_class_loadout.SetValue( 0 );
+ }
+ else if ( tf_show_taunt_explanation_in_class_loadout.GetBool() && m_pTauntsExplanationPopup )
+ {
+ m_pTauntsExplanationPopup->Popup();
+ tf_show_taunt_explanation_in_class_loadout.SetValue( 0 );
+ }
+
+ ClearItemOptionsMenu();
+ }
+ else
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->ClearCarriedItems();
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::PostShowPanel( bool bVisible )
+{
+ if ( bVisible )
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->SetVisible( true );
+ }
+
+ if ( m_pBuildablesButton )
+ {
+ m_pBuildablesButton->SetVisible( m_iCurrentClassIndex == TF_CLASS_ENGINEER );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::SetClass( int iClass )
+{
+ m_iCurrentClassIndex = iClass;
+
+ if ( m_pLoadoutPresetPanel )
+ {
+ m_pLoadoutPresetPanel->SetClass( m_iCurrentClassIndex );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::SetTeam( int iTeam )
+{
+ Assert( IsValidTFTeam( iTeam ) );
+ m_iCurrentTeamIndex = iTeam;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CClassLoadoutPanel::GetNumRelevantSlots() const
+{
+ return m_pItemModelPanels.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconItemView *CClassLoadoutPanel::GetItemInSlot( int iSlot )
+{
+ if( iSlot >= 0 && iSlot < m_pItemModelPanels.Count() )
+ {
+ return m_pItemModelPanels[iSlot]->GetItem();
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::FireGameEvent( IGameEvent *event )
+{
+ // If we're not visible, ignore all events
+ if ( !IsVisible() )
+ return;
+
+ BaseClass::FireGameEvent( event );
+
+ // We need to update ourselves after the base has done it, so our item models have been updated
+ const char *type = event->GetName();
+ if ( Q_strcmp( "inventory_updated", type ) == 0 )
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex );
+ }
+ }
+}
+
+void CClassLoadoutPanel::AddNewItemPanel( int iPanelIndex )
+{
+ BaseClass::AddNewItemPanel( iPanelIndex );
+
+ m_vecItemOptionButtons[ m_vecItemOptionButtons.AddToTail() ] = new CExButton( this,
+ CFmtStr( "item_options_button%d", iPanelIndex ),
+ "+",
+ this,
+ CFmtStr( "options%d", iPanelIndex ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::UpdateModelPanels( void )
+{
+ // Search for a Robot Costume
+ bool bIsRobot = false;
+ static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" );
+ // For now, fill them out with the local player's currently wielded items
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
+ if ( !pItemData )
+ continue;
+ if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) )
+ {
+ bIsRobot = true;
+ break;
+ }
+ }
+
+ // We're showing the loadout for a specific class.
+ TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex );
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->ClearCarriedItems();
+ m_pPlayerModelPanel->SetToPlayerClass( m_iCurrentClassIndex, bIsRobot );
+ m_pPlayerModelPanel->SetTeam( m_iCurrentTeamIndex );
+ }
+
+ // For now, fill them out with the local player's currently wielded items
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
+ m_pItemModelPanels[i]->SetItem( pItemData );
+ m_pItemModelPanels[i]->SetShowQuantity( true );
+ m_pItemModelPanels[i]->SetSelected( false );
+ SetBorderForItem( m_pItemModelPanels[i], false );
+
+ if ( m_pPlayerModelPanel && pItemData && pItemData->IsValid() )
+ {
+ m_pPlayerModelPanel->AddCarriedItem( pItemData );
+ }
+ }
+
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->HoldItemInSlot( m_iCurrentSlotIndex );
+ }
+
+ SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( pData->m_szLocalizableName ) );
+
+ UpdatePassiveAttributes();
+
+ // Now layout again to position our item buttons
+ InvalidateLayout();
+
+ if ( m_pItemOptionPanel->IsVisible() )
+ {
+ m_pItemOptionPanel->UpdateItemOptionsUI();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i] == pItemPanel )
+ {
+ OnCommand( VarArgs("change%d", i) );
+ return;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnSelectionReturned( KeyValues *data )
+{
+ if ( data )
+ {
+ uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );
+
+ // ulIndex implies do nothing (escape key)
+ if ( ulIndex != 0 )
+ {
+ TFInventoryManager()->EquipItemInLoadout( m_iCurrentClassIndex, m_iCurrentSlotIndex, ulIndex );
+
+ m_bLoadoutHasChanged = true;
+
+ UpdateModelPanels();
+
+ // Send the preset panel a msg so it can save the change
+ KeyValues *pLoadoutChangedMsg = new KeyValues( "LoadoutChanged" );
+ pLoadoutChangedMsg->SetInt( "slot", m_iCurrentSlotIndex );
+ pLoadoutChangedMsg->SetUint64( "itemid", ulIndex );
+ PostMessage( m_pLoadoutPresetPanel, pLoadoutChangedMsg );
+ }
+ }
+
+ PostMessage( GetParent(), new KeyValues("SelectionEnded") );
+
+ // It'll have deleted itself, so we don't need to clean it up
+ m_pSelectionPanel = NULL;
+ OnCancelSelection();
+
+ // find the selected item and give it the focus
+ CItemModelPanel *pSelection = GetFirstSelectedItemModelPanel( true );
+ if( !pSelection )
+ {
+ m_pItemModelPanels[0]->SetSelected( true );
+ pSelection = m_pItemModelPanels[0];
+ }
+
+ pSelection->RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnCancelSelection( void )
+{
+ if ( m_pSelectionPanel )
+ {
+ m_pSelectionPanel->SetVisible( false );
+ m_pSelectionPanel->MarkForDeletion();
+ m_pSelectionPanel = NULL;
+ }
+
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::RespawnPlayer()
+{
+ if ( tf_respawn_on_loadoutchanges.GetBool() )
+ {
+ // Tell the GC to tell server that we should respawn if we're in a respawn room
+ GCSDK::CGCMsg< MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange );
+ GCClientSystem()->BSendMessage( msg );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Apply KVs to the item option buttons
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::ApplyKVsToItemPanels( void )
+{
+ BaseClass::ApplyKVsToItemPanels();
+
+ if ( m_pItemOptionPanelKVs )
+ {
+ for ( int i = 0; i < m_vecItemOptionButtons.Count(); i++ )
+ {
+ m_vecItemOptionButtons[i]->ApplySettings( m_pItemOptionPanelKVs );
+ m_vecItemOptionButtons[i]->InvalidateLayout();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnClosing( void )
+{
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->ClearCarriedItems();
+ }
+
+ if ( m_bLoadoutHasChanged )
+ {
+ RespawnPlayer();
+
+ m_bLoadoutHasChanged = false;
+ }
+}
+
+extern const char *g_szItemBorders[AE_MAX_TYPES][5];
+extern ConVar cl_showbackpackrarities;
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
+{
+ if ( !pItemPanel )
+ return;
+
+ const char *pszBorder = NULL;
+
+ if ( pItemPanel->IsGreyedOut() )
+ {
+ pszBorder = "EconItemBorder";
+ }
+ else
+ {
+ int iRarity = 0;
+ if ( pItemPanel->HasItem() && cl_showbackpackrarities.GetBool() )
+ {
+ iRarity = pItemPanel->GetItem()->GetItemQuality();
+
+ uint8 nRarity = pItemPanel->GetItem()->GetItemDefinition()->GetRarity();
+ if ( ( nRarity != k_unItemRarity_Any ) && ( iRarity != AE_SELFMADE ) )
+ {
+ // translate this quality to rarity
+ iRarity = nRarity + AE_RARITY_DEFAULT;
+ }
+
+ if ( iRarity > 0 )
+ {
+ if ( bMouseOver || pItemPanel->IsSelected() )
+ {
+ pszBorder = g_szItemBorders[iRarity][1];
+ }
+ else
+ {
+ pszBorder = g_szItemBorders[iRarity][0];
+ }
+ }
+ }
+
+
+ if ( iRarity == 0 )
+ {
+ if ( bMouseOver || pItemPanel->IsSelected() )
+ {
+ pszBorder = "LoadoutItemMouseOverBorder";
+ }
+ else
+ {
+ pszBorder = "EconItemBorder";
+ }
+ }
+ }
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clear the item options menu and reset the button that summoned it
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::ClearItemOptionsMenu( void )
+{
+ SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" );
+ m_pItemOptionPanel->SetItemSlot( LOADOUT_POSITION_INVALID, m_iCurrentClassIndex );
+ m_pItemOptionPanel->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Safely set the text for a button
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::SetOptionsButtonText( int nIndex, const char* pszText )
+{
+ if ( nIndex >= 0 && nIndex < m_vecItemOptionButtons.Count() )
+ {
+ m_vecItemOptionButtons[ m_pItemOptionPanel->GetItemSlot() ]->SetText( pszText );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return if the passed in item has any options
+//-----------------------------------------------------------------------------
+bool CClassLoadoutPanel::AnyOptionsAvailableForItem( const CEconItemView *pItem )
+{
+ if ( !pItem )
+ return false;
+
+ // Styles!
+ if ( pItem->GetStaticData()->GetNumSelectableStyles() > 1 )
+ return true;
+
+ // Unusual particle effect! For Cosmetics only
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ if ( pItem->FindAttribute( pAttrDef_AttachParticleEffect ) && pItem->GetItemDefinition()->GetLoadoutSlot( 0 ) >= LOADOUT_POSITION_HEAD )
+ return true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::SetLoadoutPage( classloadoutpage_t loadoutPage )
+{
+ ClearItemOptionsMenu();
+ switch ( loadoutPage )
+ {
+ case CHARACTER_LOADOUT_PAGE:
+ {
+ m_bInTauntLoadoutMode = false;
+ }
+ break;
+ case TAUNT_LOADOUT_PAGE:
+ {
+ m_bInTauntLoadoutMode = true;
+ }
+ break;
+ default:
+ {
+ // Unhandled loadout page
+ Assert( 0 );
+ }
+ }
+ InvalidateLayout();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "characterloadout" ) )
+ {
+ SetLoadoutPage( CHARACTER_LOADOUT_PAGE );
+ return;
+ }
+ else if ( FStrEq( command, "tauntloadout" ) )
+ {
+ SetLoadoutPage( TAUNT_LOADOUT_PAGE );
+ return;
+ }
+ else if ( !V_strnicmp( command, "change", 6 ) )
+ {
+ const char *pszNum = command+6;
+ if ( pszNum && pszNum[0] )
+ {
+ int iSlot = atoi(pszNum);
+ if ( iSlot >= 0 && iSlot < CLASS_LOADOUT_POSITION_COUNT && m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
+ {
+ if ( m_iCurrentSlotIndex != iSlot )
+ {
+ m_iCurrentSlotIndex = iSlot;
+ }
+
+ // Create the selection screen. It removes itself on close.
+ m_pSelectionPanel = new CEquipSlotItemSelectionPanel( this, m_iCurrentClassIndex, iSlot );
+ m_pSelectionPanel->ShowPanel( 0, true );
+
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->SetVisible( false );
+ }
+
+ ClearItemOptionsMenu();
+
+ PostMessage( GetParent(), new KeyValues("SelectionStarted") );
+ }
+ }
+
+ return;
+ }
+ else if ( !V_strnicmp( command, "options", 7 ) )
+ {
+ const char *pszNum = command + 7;
+ if( pszNum && pszNum[0] )
+ {
+ int iSlot = atoi( pszNum );
+ //iSlot = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[iSlot - 1];
+ if ( iSlot >= 0 && iSlot < m_vecItemOptionButtons.Count() && m_iCurrentClassIndex != TF_CLASS_UNDEFINED )
+ {
+ // Change the button we're coming from to be a "+"
+ SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), "+" );
+
+ // Update the current slot index for callback from the setstyle button.
+ // It will send us a message to change the item the player model is holding
+ // and we need this to be updated for that.
+ m_iCurrentSlotIndex = iSlot;
+
+
+ // Did they just toggle?
+ if ( m_pItemOptionPanel->GetItemSlot() == iSlot )
+ {
+ m_pItemOptionPanel->SetVisible( !m_pItemOptionPanel->IsVisible() );
+ }
+ else
+ {
+ // Set the options panel to have the data for this slot
+ m_pItemOptionPanel->SetItemSlot( (loadout_positions_t)iSlot, m_iCurrentClassIndex );
+ m_pItemOptionPanel->SetVisible( true );
+ // Figure out if this is on the left or right
+ int iColumnHeight = 4;
+ int iColumn = iSlot / iColumnHeight;
+ PinCorner_e myCornerToPin = iColumn == 0 ? PIN_TOPLEFT : PIN_TOPRIGHT;
+ PinCorner_e siblingCornerPinTo = iColumn == 0 ? PIN_TOPRIGHT : PIN_TOPLEFT;
+ // Pin to the appropriate side
+ int iButtonPos = g_VisibleLoadoutSlotsPerClass[m_iCurrentClassIndex]->m_iPos[ iSlot ] - 1;
+ m_pItemOptionPanel->PinToSibling( m_vecItemOptionButtons[ iButtonPos ]->GetName(), myCornerToPin, siblingCornerPinTo );
+ m_pItemOptionPanel->UpdateItemOptionsUI();
+ }
+
+ // Change the button we're going to to be "-" if we're visible, "+" if we're not
+ SetOptionsButtonText( m_pItemOptionPanel->GetItemSlot(), m_pItemOptionPanel->IsVisible() ? "-" : "+" );
+ }
+ return;
+ }
+ }
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel )
+{
+ BaseClass::OnMessage( pParams, hFromPanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+struct passive_attrib_to_print_t
+{
+ const CEconItemAttributeDefinition *m_pAttrDef;
+ attrib_value_t m_value;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CAttributeIterator_AddPassiveAttribsToPassiveList : public CEconItemSpecificAttributeIterator
+{
+public:
+ CAttributeIterator_AddPassiveAttribsToPassiveList( CUtlVector<passive_attrib_to_print_t> *pList, bool bForceAdd )
+ : m_pList( pList )
+ , m_bForceAdd( bForceAdd )
+ {
+ Assert( m_pList );
+ }
+
+ virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value )
+ {
+ Assert( pAttrDef );
+
+ if ( pAttrDef->IsHidden() )
+ return true;
+
+ if ( !m_bForceAdd )
+ {
+ const char *pDesc = pAttrDef->GetArmoryDescString();
+ if ( !pDesc || !pDesc[0] )
+ return true;
+
+ // If we have the "on_wearer" key, we're a passive attribute
+ if ( !Q_stristr(pDesc, "on_wearer") )
+ return true;
+ }
+
+ // Now see if we're already in the list
+ FOR_EACH_VEC( (*m_pList), i )
+ {
+ passive_attrib_to_print_t& passiveAttr = (*m_pList)[i];
+
+ Assert( passiveAttr.m_pAttrDef );
+
+ // We match if our class is the same -- this is a case-sensitive compare!
+ if ( Q_strcmp( passiveAttr.m_pAttrDef->GetAttributeClass(), pAttrDef->GetAttributeClass() ) )
+ continue;
+
+ // We've found a matching attribute. Collate our values and stomp over the earlier value.
+ passiveAttr.m_value = CollateAttributeValues( passiveAttr.m_pAttrDef, passiveAttr.m_value, pAttrDef, value );
+
+ return true;
+ }
+
+ // We didn't find it. Add it to the list.
+ passive_attrib_to_print_t newPassiveAttr = { pAttrDef, value };
+ m_pList->AddToTail( newPassiveAttr );
+
+ return true;
+ }
+
+ // Other types are ignored.
+
+private:
+ CUtlVector<passive_attrib_to_print_t> *m_pList;
+ bool m_bForceAdd;
+};
+
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::UpdatePassiveAttributes( void )
+{
+ if ( !m_pPassiveAttribsLabel )
+ return;
+
+ // We build a list of attributes & associated values by looping through all equipped items.
+ // This way we can identify & collate attributes based on the same definition index.
+ CUtlVector<passive_attrib_to_print_t> vecAttribsToPrint;
+
+ // Loop through all equipped items
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, i );
+ if ( pItemData && pItemData->IsValid() )
+ {
+ CAttributeIterator_AddPassiveAttribsToPassiveList attrItPassives( &vecAttribsToPrint, false );
+ pItemData->IterateAttributes( &attrItPassives );
+ }
+ }
+
+ // Then add any set bonuses
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
+ CUtlVector<const CEconItemSetDefinition *> pActiveSets;
+ TFInventoryManager()->GetActiveSets( &pActiveSets, localSteamID, m_iCurrentClassIndex );
+
+ FOR_EACH_VEC( pActiveSets, set )
+ {
+ CAttributeIterator_AddPassiveAttribsToPassiveList attrItSetPassives( &vecAttribsToPrint, true );
+ pActiveSets[set]->IterateAttributes( &attrItSetPassives );
+ }
+ }
+
+ // Now build the text
+ wchar_t wszPassiveDesc[4096];
+ wszPassiveDesc[0] = '\0';
+ m_pPassiveAttribsLabel->GetTextImage()->ClearColorChangeStream();
+
+ wchar_t *pHeader = g_pVGuiLocalize->Find( "#TF_PassiveAttribs" );
+ if ( pHeader )
+ {
+ V_wcscpy_safe( wszPassiveDesc, pHeader );
+ V_wcscat_safe( wszPassiveDesc, L"\n" );
+ }
+
+ if ( vecAttribsToPrint.Count() )
+ {
+ FOR_EACH_VEC( vecAttribsToPrint, i )
+ {
+ CEconAttributeDescription AttrDesc( GLocalizationProvider(), vecAttribsToPrint[i].m_pAttrDef, vecAttribsToPrint[i].m_value );
+ AddAttribPassiveText( AttrDesc, wszPassiveDesc, ARRAYSIZE(wszPassiveDesc) );
+ }
+ }
+ else
+ {
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ Color col = pScheme->GetColor( GetColorNameForAttribColor( ATTRIB_COL_NEUTRAL ), Color(255,255,255,255) );
+ m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( wszPassiveDesc ) );
+
+ wchar_t *pNone = g_pVGuiLocalize->Find( "#TF_PassiveAttribs_None" );
+ if ( pNone )
+ {
+ V_wcscat_safe( wszPassiveDesc, pNone );
+ }
+ }
+
+ m_pPassiveAttribsLabel->SetText( wszPassiveDesc );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::AddAttribPassiveText( const CEconAttributeDescription& AttrDesc, INOUT_Z_CAP(iNumPassiveChars) wchar_t *out_wszPassiveDesc, int iNumPassiveChars )
+{
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ Assert( pScheme );
+
+ if ( !AttrDesc.GetDescription().IsEmpty() )
+ {
+ // Insert the color change at the current position
+ Color col = pScheme->GetColor( GetColorNameForAttribColor( AttrDesc.GetDefaultColor() ), Color(255,255,255,255) );
+ m_pPassiveAttribsLabel->GetTextImage()->AddColorChange( col, Q_wcslen( out_wszPassiveDesc ) );
+
+ // Now append the text of the attribute
+ V_wcsncat( out_wszPassiveDesc, AttrDesc.GetDescription().Get(), iNumPassiveChars );
+ V_wcsncat( out_wszPassiveDesc, L"\n", iNumPassiveChars );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CClassLoadoutPanel::UpdatePageButtonColor( CExImageButton *pPageButton, bool bIsActive )
+{
+ if ( pPageButton )
+ {
+ int iLoaded = bIsActive ? LOADED : NOTLOADED;
+ pPageButton->SetDefaultColor( m_aDefaultColors[iLoaded][FG][DEFAULT], m_aDefaultColors[iLoaded][BG][DEFAULT] );
+ pPageButton->SetArmedColor( m_aDefaultColors[iLoaded][FG][ARMED], m_aDefaultColors[iLoaded][BG][ARMED] );
+ pPageButton->SetDepressedColor( m_aDefaultColors[iLoaded][FG][DEPRESSED], m_aDefaultColors[iLoaded][BG][DEPRESSED] );
+ }
+}
diff --git a/game/client/tf/vgui/class_loadout_panel.h b/game/client/tf/vgui/class_loadout_panel.h
new file mode 100644
index 0000000..894b0e6
--- /dev/null
+++ b/game/client/tf/vgui/class_loadout_panel.h
@@ -0,0 +1,157 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CLASS_LOADOUT_PANEL_H
+#define CLASS_LOADOUT_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "base_loadout_panel.h"
+#include "tf_playermodelpanel.h"
+#include "item_selection_panel.h"
+#include <../common/GameUI/cvarslider.h>
+#include <vgui/VGUI.h>
+#include "vgui_controls/CheckButton.h"
+
+#define NUM_ITEM_PANELS_IN_LOADOUT CLASS_LOADOUT_POSITION_COUNT
+
+class CLoadoutPresetPanel;
+
+class CLoadoutItemOptionsPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CLoadoutItemOptionsPanel, vgui::EditablePanel );
+public:
+ CLoadoutItemOptionsPanel( Panel *parent, const char *pName );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+
+ virtual void OnCommand( const char *command );
+ virtual void OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel );
+
+ void SetItemSlot( loadout_positions_t eItemSlot, int iClassIndex );
+ loadout_positions_t GetItemSlot() const { return m_eItemSlot; }
+ void UpdateItemOptionsUI();
+
+private:
+
+ void AddControlsParticleEffect( void ) const;
+ void AddControlsSetStyle( void ) const;
+ CEconItemView* GetItem( void ) const;
+
+ class vgui::PanelListPanel *m_pListPanel;
+ CCvarSlider *m_pHatParticleSlider;
+ CExButton *m_pSetStyleButton;
+ vgui::CheckButton *m_pHatParticleUseHeadButton;
+
+ int m_iCurrentClassIndex;
+ loadout_positions_t m_eItemSlot;
+};
+
+
+//-----------------------------------------------------------------------------
+// A loadout screen that handles modifying the loadout of a specific class
+//-----------------------------------------------------------------------------
+class CClassLoadoutPanel : public CBaseLoadoutPanel
+{
+ DECLARE_CLASS_SIMPLE( CClassLoadoutPanel, CBaseLoadoutPanel );
+public:
+ CClassLoadoutPanel( vgui::Panel *parent );
+ ~CClassLoadoutPanel();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ virtual void AddNewItemPanel( int iPanelIndex ) OVERRIDE;
+ virtual void UpdateModelPanels( void );
+ virtual int GetNumItemPanels( void ) { return NUM_ITEM_PANELS_IN_LOADOUT; };
+ virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory );
+ virtual void PostShowPanel( bool bVisible );
+ virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE;
+ virtual void OnNavigateTo( const char* panelName ) OVERRIDE;
+ virtual void OnNavigateFrom( const char* panelName ) OVERRIDE;
+
+ void SetClass( int iClass );
+ void SetTeam( int iTeam );
+
+ int GetNumRelevantSlots() const;
+ CEconItemView *GetItemInSlot( int iSlot );
+
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel );
+ MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data );
+ MESSAGE_FUNC( OnCancelSelection, "CancelSelection" );
+ MESSAGE_FUNC( OnClosing, "Closing" );
+ virtual void OnCommand( const char *command );
+ virtual void OnMessage( const KeyValues* pParams, vgui::VPANEL hFromPanel );
+
+ void SetSelectionPanel( CEquipSlotItemSelectionPanel *pPanel ) { m_pSelectionPanel = pPanel; }
+ void UpdatePassiveAttributes( void );
+
+ bool IsInSelectionPanel() const { return m_pSelectionPanel != NULL; }
+ CEquipSlotItemSelectionPanel *GetItemSelectionPanel() { return m_pSelectionPanel; }
+
+ bool IsEditingTauntSlots() const { return m_bInTauntLoadoutMode; }
+
+ enum classloadoutpage_t
+ {
+ CHARACTER_LOADOUT_PAGE,
+ TAUNT_LOADOUT_PAGE
+ };
+ void SetLoadoutPage( classloadoutpage_t loadoutPage );
+
+protected:
+ virtual void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver );
+ void AddAttribPassiveText( const class CEconAttributeDescription& AttrDesc, INOUT_Z_CAP(iNumPassiveChars) wchar_t *out_wszPassiveDesc, int iNumPassiveChars );
+ void RespawnPlayer();
+ virtual void ApplyKVsToItemPanels( void ) OVERRIDE;
+ void ClearItemOptionsMenu( void );
+ void SetOptionsButtonText( int nIndex, const char* pszText );
+ static bool AnyOptionsAvailableForItem( const CEconItemView *pItem );
+
+ int m_iCurrentClassIndex;
+ int m_iCurrentTeamIndex;
+ int m_iCurrentSlotIndex;
+ bool m_bLoadoutHasChanged;
+ bool m_bInTauntLoadoutMode;
+ CTFPlayerModelPanel *m_pPlayerModelPanel;
+ CEquipSlotItemSelectionPanel *m_pSelectionPanel;
+ vgui::Label *m_pTauntHintLabel;
+ CExLabel *m_pTauntLabel;
+ CExLabel *m_pTauntCaratLabel;
+ CExLabel *m_pPassiveAttribsLabel;
+ Panel *m_pTopLinePanel;
+
+ CExButton *m_pBuildablesButton;
+ CExImageButton *m_pCharacterLoadoutButton;
+ CExImageButton *m_pTauntLoadoutButton;
+
+ CLoadoutPresetPanel *m_pLoadoutPresetPanel;
+
+ CExplanationPopup *m_pPresetsExplanationPopup;
+ CExplanationPopup *m_pTauntsExplanationPopup;
+
+ KeyValues *m_pItemOptionPanelKVs;
+ CUtlVector< CExButton * > m_vecItemOptionButtons;
+ CLoadoutItemOptionsPanel *m_pItemOptionPanel;
+
+private:
+ void UpdatePageButtonColor( CExImageButton *pPageButton, bool bIsActive );
+
+ enum PageButtonColors_t
+ {
+ LOADED = 0, NOTLOADED,
+ FG = 0, BG,
+ DEFAULT = 0, ARMED, DEPRESSED
+ };
+ Color m_aDefaultColors[2][2][3]; // [LOADED|NOTLOADED][FG|BG][DEFAULT|ARMED|DEPRESSED]
+};
+
+extern CClassLoadoutPanel *g_pClassLoadoutPanel;
+
+#endif // CLASS_LOADOUT_PANEL_H
diff --git a/game/client/tf/vgui/collection_crafting_panel.cpp b/game/client/tf/vgui/collection_crafting_panel.cpp
new file mode 100644
index 0000000..4794a4c
--- /dev/null
+++ b/game/client/tf/vgui/collection_crafting_panel.cpp
@@ -0,0 +1,829 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "collection_crafting_panel.h"
+#include "cdll_client_int.h"
+#include "ienginevgui.h"
+#include "econ_item_tools.h"
+#include "econ_ui.h"
+#include <vgui_controls/AnimationController.h>
+#include "clientmode_tf.h"
+#include "softline.h"
+#include "drawing_panel.h"
+#include "tf_item_inventory.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCollectionCraftingPanel::CCollectionCraftingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip )
+ : BaseClass( parent, "CollectionCraftingPanel" )
+ , m_pKVItemPanels( NULL )
+ , m_pModelPanel( NULL )
+ , m_bWaitingForGCResponse( false )
+ , m_bEnvelopeReadyToSend( false )
+ , m_pMouseOverTooltip( pTooltip )
+ , m_bShowing( false )
+ , m_bShowImmediately( false )
+{
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_pSelectingItemModelPanel = NULL;
+
+ m_pTradeUpContainer = new EditablePanel( this, "TradeUpContainer" );
+ m_pInspectPanel = new CTFItemInspectionPanel( this, "NewItemPanel" );
+ m_pCosmeticResultItemModelPanel = new CItemModelPanel( m_pInspectPanel, "CosmeticResultItemModelPanel" );
+ m_pStampPanel = new ImagePanel( this, "Stamp" );
+ m_pStampButton = new CExButton( this, "ApplyStampButton", "" );
+
+ EditablePanel* pPaperContainer = new EditablePanel( m_pTradeUpContainer, "PaperContainer" );
+
+ m_pOKButton = new CExButton( pPaperContainer, "OkButton", "" );
+ m_pNextItemButton = new CExButton( this, "NextItemButton", "" );
+
+ m_pDrawingPanel = new CDrawingPanel( this, "drawingpanel" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCollectionCraftingPanel::~CCollectionCraftingPanel( void )
+{
+ if ( m_hSelectionPanel )
+ {
+ m_hSelectionPanel->MarkForDeletion();
+ }
+
+ if ( m_pKVItemPanels )
+ {
+ m_pKVItemPanels->deleteThis();
+ }
+}
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::SetItemPanelCount( )
+{
+ // only do this once
+ if ( m_vecItemContainers.Count() != 0 )
+ return;
+
+ const int nNumItems = GetInputItemCount();
+ const int nNumOutput = GetOutputItemCount();
+
+ EditablePanel* pPaperContainer = dynamic_cast<vgui::EditablePanel*>( m_pTradeUpContainer->FindChildByName( "PaperContainer" ) );
+ if ( pPaperContainer )
+ {
+ m_vecItemContainers.SetCount( nNumItems );
+ FOR_EACH_VEC( m_vecItemContainers, i )
+ {
+ m_vecItemContainers[i] = new EditablePanel( pPaperContainer, "itemcontainer" );
+ }
+
+ m_vecOutputItemContainers.SetCount( nNumOutput );
+ FOR_EACH_VEC( m_vecOutputItemContainers, i )
+ {
+ m_vecOutputItemContainers[i] = new EditablePanel( pPaperContainer, "itemcontainer" );
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::CreateSelectionPanel()
+{
+ m_hSelectionPanel = new CCollectionCraftingSelectionPanel( this );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetResFile() );
+
+ m_pModelPanel = FindControl< CBaseModelPanel >( "ReturnModel" );
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetLookAtCamera( false );
+ }
+
+ if ( m_pDrawingPanel )
+ {
+ m_pDrawingPanel->SetType( DRAWING_PANEL_TYPE_CRAFTING );
+ }
+
+ m_pItemNamePanel = m_pInspectPanel->FindControl< CItemModelPanel >( "ItemName" );
+ Assert( m_pItemNamePanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "ItemContainerKV" );
+ if ( pItemKV )
+ {
+ if ( m_pKVItemPanels )
+ {
+ m_pKVItemPanels->deleteThis();
+ }
+ m_pKVItemPanels = new KeyValues("ItemContainerKV");
+ pItemKV->CopySubkeys( m_pKVItemPanels );
+ }
+
+ m_vecImagePanels.Purge();
+ m_vecItemPanels.Purge();
+
+ KeyValues *pBoxTopsKV = inResourceData->FindKey( "BoxTops" );
+ if ( pBoxTopsKV )
+ {
+ m_vecBoxTopNames.Purge();
+ FOR_EACH_VALUE( pBoxTopsKV, pValue )
+ {
+ m_vecBoxTopNames.AddToTail( pValue->GetString() );
+ }
+ }
+ Assert( m_vecBoxTopNames.Count() );
+
+ KeyValues *pStampNames = inResourceData->FindKey( "stampimages" );
+ if ( pStampNames )
+ {
+ m_vecStampNames.Purge();
+ FOR_EACH_VALUE( pStampNames, pValue )
+ {
+ m_vecStampNames.AddToTail( pValue->GetString() );
+ }
+ }
+ Assert( m_vecStampNames.Count() );
+
+ KeyValues *pResulStrings = inResourceData->FindKey( "resultstring" );
+ if ( pResulStrings )
+ {
+ m_vecResultStrings.Purge();
+ FOR_EACH_VALUE( pResulStrings, pValue )
+ {
+ m_vecResultStrings.AddToTail( pValue->GetString() );
+ }
+ }
+ Assert( m_vecResultStrings.Count() );
+
+ KeyValues *pLocalizedPanelNames = inResourceData->FindKey( "localizedpanels" );
+ if ( pLocalizedPanelNames )
+ {
+ m_vecLocalizedPanels.Purge();
+ FOR_EACH_TRUE_SUBKEY( pLocalizedPanelNames, pValue )
+ {
+ m_vecLocalizedPanels.AddToTail( { pValue->GetString( "panelname" ), pValue->GetBool( "show_for_english", false ) } );
+ }
+ }
+
+ CreateItemPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetMDL( "models/player/items/crafting/mannco_crate_tradeup.mdl" );
+ }
+
+ FOR_EACH_VEC( m_vecItemContainers, i )
+ {
+ m_vecItemContainers[ i ]->SetPos( m_iButtonsStartX + m_iButtonsStepX * ( i % 5 )
+ , m_iButtonsStartY + m_iButtonsStepY * ( i / 5 ) );
+ }
+
+ FOR_EACH_VEC( m_vecOutputItemContainers, i )
+ {
+ m_vecOutputItemContainers[i]->SetPos( m_iOutputItemStartX + m_iOutputItemStepX * ( i % 5 )
+ , m_iOutputItemStartY + m_iOutputItemStepY * ( i / 5 ) );
+ }
+
+ if ( steamapicontext && steamapicontext->SteamApps() )
+ {
+ char uilanguage[ 64 ];
+ uilanguage[0] = 0;
+ engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
+ ELanguage language = PchLanguageToELanguage( uilanguage );
+
+ FOR_EACH_VEC( m_vecLocalizedPanels, i )
+ {
+ bool bShow = language == k_Lang_English && m_vecLocalizedPanels[ i ].m_bShowForEnglish;
+ Panel* pPanel = m_pTradeUpContainer->FindChildByName( m_vecLocalizedPanels[ i ].m_strPanel, true );
+ if ( pPanel )
+ {
+ pPanel->SetVisible( bShow );
+ }
+ }
+ }
+
+ UpdateOKButton();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::CreateItemPanels()
+{
+ SetItemPanelCount();
+
+ m_vecImagePanels.SetCount( m_vecItemContainers.Count() );
+ m_vecItemPanels.SetCount( m_vecItemContainers.Count() );
+
+ FOR_EACH_VEC( m_vecItemContainers, i )
+ {
+ m_vecItemContainers[ i ]->ApplySettings( m_pKVItemPanels );
+ m_vecImagePanels[ i ] = m_vecItemContainers[ i ]->FindControl< ImagePanel >( "imagepanel" );
+ m_vecItemPanels[ i ] = m_vecItemContainers[ i ]->FindControl< CItemModelPanel >( "itempanel" );
+ m_vecItemPanels[ i ]->SetActAsButton( true, true );
+ m_vecItemPanels[ i ]->SetTooltip( m_pMouseOverTooltip, "" );
+
+ CExButton* pButton = m_vecItemContainers[ i ]->FindControl< CExButton >( "BackgroundButton" );
+ if ( pButton )
+ {
+ pButton->SetCommand( CFmtStr( "select%d", i ) );
+ pButton->AddActionSignalTarget( this );
+ }
+ }
+
+ m_vecOutputImagePanels.SetCount( m_vecOutputItemContainers.Count() );
+ m_vecOutputItemPanels.SetCount( m_vecOutputItemContainers.Count() );
+
+ FOR_EACH_VEC( m_vecOutputItemContainers, i )
+ {
+ m_vecOutputItemContainers[i]->ApplySettings( m_pKVItemPanels );
+ m_vecOutputImagePanels[i] = m_vecOutputItemContainers[i]->FindControl< ImagePanel >( "imagepanel" );
+ m_vecOutputItemPanels[i] = m_vecOutputItemContainers[i]->FindControl< CItemModelPanel >( "itempanel" );
+ m_vecOutputItemPanels[i]->SetTooltip( m_pMouseOverTooltip, "" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( false, true );
+ return;
+ }
+
+
+ if ( FStrEq( "doneselectingitems", command ) )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterStart" );
+ m_bEnvelopeReadyToSend = false;
+
+ if ( m_vecStampNames.Count() )
+ {
+ m_pStampPanel->SetImage( m_vecStampNames[ RandomInt( 0, m_vecStampNames.Count() - 1 ) ] );
+ }
+
+ return;
+ }
+ else if ( FStrEq( "cancel", command ) )
+ {
+ SetVisible( false );
+ return;
+ }
+ else if ( Q_strnicmp( "select", command, 6 ) == 0 )
+ {
+ SelectPanel( atoi( command + 6 ) );
+ return;
+ }
+ else if ( FStrEq( "envelopesend", command ) )
+ {
+ GCSDK::CProtoBufMsg<CMsgCraftCollectionUpgrade> msg( k_EMsgGCCraftCollectionUpgrade );
+
+ // Construct message
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[ i ]->GetItem() == NULL )
+ return;
+
+ msg.Body().add_item_id( m_vecItemPanels[ i ]->GetItem()->GetItemID() );
+ }
+ // Send if off
+ GCClientSystem()->BSendMessage( msg );
+
+ m_bWaitingForGCResponse = true;
+ m_nFoundItemID.Purge();
+ m_timerResponse.Start( 5.f );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterSend" );
+ return;
+ }
+ else if ( FStrEq( "placestamp", command ) )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_PlaceStamp" );
+ return;
+ }
+ else if( Q_strnicmp( "playcratesequence", command, 17 ) == 0 )
+ {
+ m_pModelPanel->SetSequence( atoi( command + 17 ), true );
+ return;
+ }
+ else if( FStrEq( "itemget", command ) )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ItemRecieved" );
+ wchar_t *pszLocalized = NULL;
+
+ //
+ if ( m_eEconItemOrigin == kEconItemOrigin_FoundInCrate )
+ {
+ pszLocalized = g_pVGuiLocalize->Find( "#NewItemMethod_FoundInCrate" );
+ }
+ else if ( m_vecResultStrings.Count() )
+ {
+ pszLocalized = g_pVGuiLocalize->Find( m_vecResultStrings[ RandomInt( 0, m_vecResultStrings.Count() - 1 ) ] );
+ }
+
+ m_pInspectPanel->SetDialogVariable( "resultstring", pszLocalized );
+ return;
+ }
+ else if( Q_strnicmp( "playsound", command, 9 ) == 0 )
+ {
+ vgui::surface()->PlaySound( command + 10 );
+ return;
+ }
+ else if( FStrEq( "startexplanation1", command ) )
+ {
+ CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ return;
+ }
+ else if( FStrEq( "startexplanation2", command ) )
+ {
+ CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("SigningExplanation") );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ return;
+ }
+ else if ( FStrEq( "nextitem", command ) )
+ {
+ if ( m_nFoundItemID.Count() > 1 )
+ {
+ // Remove head, reset timer to drop next item
+ m_nFoundItemID.Remove( 0 );
+ m_timerResponse.Start( 5.f );
+ m_bShowImmediately = true;
+ }
+ }
+ else if( FStrEq( "reload", command ) )
+ {
+ g_pVGuiLocalize->ReloadLocalizationFiles();
+ InvalidateLayout( false, true );
+ SetVisible( true );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CCollectionCraftingPanel::SelectPanel( int nPanel )
+{
+ m_pSelectingItemModelPanel = m_vecItemPanels[ nPanel ];
+
+ CCopyableUtlVector< const CEconItemView* > vecCurrentItems;
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[ i ]->GetItem() )
+ {
+ vecCurrentItems.AddToTail( m_vecItemPanels[ i ]->GetItem() );
+ }
+ }
+
+ if ( !m_hSelectionPanel )
+ {
+ CreateSelectionPanel();
+ m_hSelectionPanel->SetAutoDelete( false );
+ }
+
+ if ( m_hSelectionPanel )
+ {
+ // Clicked on an item in the crafting area. Open up the selection panel.
+ m_hSelectionPanel->SetCorrespondingItems( vecCurrentItems );
+ m_hSelectionPanel->ShowDuplicateCounts( true );
+ m_hSelectionPanel->ShowPanel( 0, true );
+ m_hSelectionPanel->SetCaller( this );
+ m_hSelectionPanel->SetZPos( GetZPos() + 1 );
+ }
+}
+
+void CCollectionCraftingPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "gameui_hidden" ) )
+ {
+ SetVisible( false );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::OnItemPanelMousePressed( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() )
+ {
+ auto idx = m_vecItemPanels.Find( pItemPanel );
+ if ( idx != m_vecItemPanels.InvalidIndex() )
+ {
+ OnCommand( CFmtStr( "select%d", idx ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::OnSelectionReturned( KeyValues *data )
+{
+ Assert( m_pSelectingItemModelPanel );
+
+ m_hSelectionPanel->SetVisible( false );
+
+ if ( data && m_pSelectingItemModelPanel )
+ {
+ uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );
+
+ CEconItemView* pSelectedItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( ulIndex );
+
+ if ( pSelectedItem )
+ {
+ vgui::surface()->PlaySound( "ui/trade_up_apply_sticker.wav" );
+ }
+
+ auto idx = m_vecItemPanels.Find( m_pSelectingItemModelPanel );
+ SetItem( pSelectedItem, idx );
+ }
+
+ m_pSelectingItemModelPanel = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::UpdateOKButton()
+{
+ bool bOKEnabled = true;
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ bOKEnabled &= m_vecItemPanels[ i ]->GetItem() != NULL;
+ }
+
+ m_pOKButton->SetEnabled( bOKEnabled );
+
+ if ( bOKEnabled )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pOKButton->GetParent(), "CollectionCrafting_OKBlink" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::SetVisible( bool bVisible )
+{
+ BaseClass::SetVisible( bVisible );
+
+ if ( bVisible )
+ {
+ m_pInspectPanel->SetVisible( false );
+
+ EditablePanel* pDimmer = FindControl< EditablePanel >( "Dimmer" );
+ if ( pDimmer )
+ {
+ pDimmer->SetAlpha( 0 );
+ }
+
+ EditablePanel* pBG = FindControl< EditablePanel >( "BG" );
+ if ( pBG )
+ {
+ pBG->SetPos( pBG->GetXPos(), GetTall() );
+ }
+
+ m_pTradeUpContainer->SetVisible( true );
+ m_pTradeUpContainer->SetPos( m_pTradeUpContainer->GetXPos(), -700 );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_Intro" );
+ vgui::surface()->PlaySound( "ui/trade_up_panel_slide.wav" );
+
+ m_pDrawingPanel->ClearLines( GetLocalPlayerIndex() );
+ }
+ else
+ {
+ if ( m_hSelectionPanel )
+ {
+ m_hSelectionPanel->SetVisible( false );
+ }
+
+ if ( m_bShowing )
+ {
+ EconUI()->SetPreventClosure( false );
+ }
+ m_bShowing = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ if ( m_bWaitingForGCResponse )
+ {
+ if( pObject->GetTypeID() != CEconItem::k_nTypeID )
+ return;
+
+ CEconItem *pItem = (CEconItem *)pObject;
+
+ if ( IsUnacknowledged( pItem->GetInventoryToken() ) && ( pItem->GetOrigin() == m_eEconItemOrigin ) )
+ {
+ //Assert( m_nFoundItemID == INVALID_ITEM_ID );
+ //m_bWaitingForGCResponse = false;
+ m_nFoundItemID.AddToTail( pItem->GetItemID() );
+ CEconItemView* pNewEconItemView = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( pItem->GetItemID() );
+ if ( pNewEconItemView )
+ {
+ // Acknowledge the item
+ InventoryManager()->AcknowledgeItem( pNewEconItemView, true );
+ InventoryManager()->SaveAckFile();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::Show( CUtlVector< const CEconItemView* >& vecStartingItems )
+{
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ const CEconItemView* pItem = i < vecStartingItems.Count() ? vecStartingItems[ i ] : NULL;
+ SetItem( pItem, i );
+ }
+
+ m_bShowing = true;
+ EconUI()->SetPreventClosure( true );
+ SetVisible( true );
+
+ m_eEconItemOrigin = kEconItemOrigin_TradeUp;
+}
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::SetWaitingForItem( eEconItemOrigin eOrigin )
+{
+ // Clear Panels
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ SetItem( NULL, i );
+ }
+
+ m_bShowing = true;
+ EconUI()->SetPreventClosure( true );
+
+ m_pInspectPanel->SetVisible( false );
+ EditablePanel* pDimmer = FindControl< EditablePanel >( "Dimmer" );
+ if ( pDimmer )
+ {
+ pDimmer->SetAlpha( 0 );
+ }
+
+ EditablePanel* pBG = FindControl< EditablePanel >( "BG" );
+ if ( pBG )
+ {
+ pBG->SetPos( pBG->GetXPos(), GetTall() );
+ }
+
+ m_pTradeUpContainer->SetVisible( false );
+
+ // reset
+ m_pInspectPanel->SetItemCopy( NULL );
+ m_pCosmeticResultItemModelPanel->SetItem( NULL );
+
+ // Do not use Derived SetVisible since it does extra animations we do not want here
+ BaseClass::SetVisible( true );
+
+ m_eEconItemOrigin = eOrigin;
+
+ m_bWaitingForGCResponse = true;
+ m_nFoundItemID.Purge();
+ m_timerResponse.Start( 5.f );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_WaitForItemsOnly" );
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::SetItem( const CEconItemView* pItem, int nIndex )
+{
+ if ( nIndex != m_vecItemPanels.InvalidIndex() )
+ {
+ m_vecImagePanels[ nIndex ]->SetVisible( pItem != NULL );
+ m_vecItemPanels[ nIndex ]->SetVisible( pItem != NULL );
+ m_vecItemPanels[ nIndex ]->SetItem( pItem );
+
+ if ( pItem && m_vecBoxTopNames.Count() )
+ {
+ CUniformRandomStream randomStream;
+ randomStream.SetSeed( pItem->GetItemID() );
+ m_vecImagePanels[ nIndex ]->SetImage( m_vecBoxTopNames[ randomStream.RandomInt( 0, m_vecBoxTopNames.Count() - 1 ) ] );
+ }
+ }
+
+ UpdateOKButton();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCollectionCraftingPanel::OnThink()
+{
+ BaseClass::OnThink();
+ const float flSoonestAirDropTime = 2.f;
+
+ if ( m_timerResponse.HasStarted() )
+ {
+ // Elapsed is bad. This means the item server didnt get back to us
+ if ( m_timerResponse.IsElapsed() )
+ {
+ m_nFoundItemID.Purge();
+ m_bWaitingForGCResponse = false;
+ m_timerResponse.Invalidate();
+
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_HideWaiting" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ShowFailure" );
+ }
+ else if ( m_timerResponse.GetElapsedTime() > flSoonestAirDropTime || m_bShowImmediately )
+ {
+ m_bShowImmediately = false;
+ // At 2 seconds we want to either show that we're still waiting, or show the item
+ if ( m_nFoundItemID.Count() > 0 )
+ {
+ OnCommand( "itemget" );
+ m_timerResponse.Invalidate();
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_HideWaiting" );
+
+ // Setup the item in the panel
+ CEconItemView* pNewEconItemView = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( m_nFoundItemID[0] );
+ if ( pNewEconItemView )
+ {
+ static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" );
+ if ( pNewEconItemView->FindAttribute( pAttrib_WeaponAllowInspect ) )
+ {
+ m_pInspectPanel->SetItemCopy( pNewEconItemView );
+ m_pInspectPanel->SetSpecialAttributesOnly( true );
+ m_pCosmeticResultItemModelPanel->SetItem( NULL );
+ }
+ else //( IsMiscSlot( pNewEconItemView->GetStaticData()->GetDefaultLoadoutSlot() ) )
+ {
+ m_pCosmeticResultItemModelPanel->SetItem( pNewEconItemView );
+ m_pCosmeticResultItemModelPanel->SetNameOnly( false );
+ m_pInspectPanel->SetSpecialAttributesOnly( true );
+ m_pInspectPanel->SetItemCopy( NULL );
+ }
+
+ // Acknowledge the item
+ InventoryManager()->AcknowledgeItem( pNewEconItemView, true );
+ InventoryManager()->SaveAckFile();
+
+ if ( m_pItemNamePanel )
+ {
+ m_pItemNamePanel->SetItem( pNewEconItemView );
+ }
+ }
+
+ m_bWaitingForGCResponse = false;
+
+ // only show if more then 1 item in queue
+ m_pNextItemButton->SetVisible( m_nFoundItemID.Count() > 1 );
+ }
+ else
+ {
+ // Say that we're waiting
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ShowWaiting" );
+ }
+ }
+ }
+
+ bool bEnvelopReadyToSendThisFrame = true;
+ // They need to have drawn a little bit
+ bEnvelopReadyToSendThisFrame &= m_pDrawingPanel->GetLines( GetLocalPlayerIndex() ).Count() > 10;
+ // And placed a stamp
+ bEnvelopReadyToSendThisFrame &= m_pStampPanel->IsVisible();
+
+ // Show the send button?
+ if ( bEnvelopReadyToSendThisFrame && !m_bEnvelopeReadyToSend )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_ShowSendButton" );
+ }
+
+ m_bEnvelopeReadyToSend = bEnvelopReadyToSendThisFrame;
+}
+
+//* **************************************************************************************************************************************
+// Stat Clock Crafting
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCraftCommonStatClockPanel::CCraftCommonStatClockPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip )
+ : BaseClass( parent, pTooltip )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCraftCommonStatClockPanel::~CCraftCommonStatClockPanel( void )
+{
+
+}
+//-----------------------------------------------------------------------------
+void CCraftCommonStatClockPanel::Show( CUtlVector< const CEconItemView* >& vecStartingItems )
+{
+ BaseClass::Show( vecStartingItems );
+
+ // Create output
+ static CSchemaItemDefHandle pItemDef_CommonStatClock( "Common Stat Clock" );
+ m_outputItem.SetItemDefIndex( pItemDef_CommonStatClock->GetDefinitionIndex() );
+ m_outputItem.SetItemQuality( AE_UNIQUE ); // Unique by default
+ m_outputItem.SetItemLevel( 0 ); // Hide this?
+ m_outputItem.SetItemID( 0 );
+ m_outputItem.SetInitialized( true );
+
+ m_vecOutputImagePanels[0]->SetVisible( true );
+ m_vecOutputItemPanels[0]->SetVisible( true );
+ m_vecOutputItemPanels[0]->SetItem( &m_outputItem );
+
+ if ( m_vecBoxTopNames.Count() )
+ {
+ CUniformRandomStream randomStream;
+ randomStream.SetSeed( 0 );
+ m_vecOutputImagePanels[0]->SetImage( m_vecBoxTopNames[randomStream.RandomInt( 0, m_vecBoxTopNames.Count() - 1 )] );
+ }
+}
+//-----------------------------------------------------------------------------
+void CCraftCommonStatClockPanel::CreateSelectionPanel()
+{
+ CStatClockCraftingSelectionPanel *pSelectionPanel = new CStatClockCraftingSelectionPanel( this );
+ m_hSelectionPanel = (CCollectionCraftingSelectionPanel*)pSelectionPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftCommonStatClockPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( "envelopesend", command ) )
+ {
+ GCSDK::CProtoBufMsg<CMsgCraftCommonStatClock> msg( k_EMsgGCCraftCommonStatClock );
+
+ // Find out if the user owns this item or not and place in the proper bucket
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+ if ( !pLocalInv )
+ return;
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i]->GetItem() == NULL )
+ return;
+
+ msg.Body().add_item_id( m_vecItemPanels[i]->GetItem()->GetItemID() );
+ }
+ // Send if off
+ GCClientSystem()->BSendMessage( msg );
+
+ m_bWaitingForGCResponse = true;
+ m_nFoundItemID.Purge();
+ m_timerResponse.Start( 5.f );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterSend" );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/collection_crafting_panel.h b/game/client/tf/vgui/collection_crafting_panel.h
new file mode 100644
index 0000000..491b40d
--- /dev/null
+++ b/game/client/tf/vgui/collection_crafting_panel.h
@@ -0,0 +1,225 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef COLLECTION_CRAFTING_PANEL_H
+#define COLLECTION_CRAFTING_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "backpack_panel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "tf_gcmessages.h"
+#include "econ_gcmessages.h"
+#include "tf_imagepanel.h"
+#include "tf_controls.h"
+#include "item_selection_panel.h"
+#include "drawing_panel.h"
+#include "local_steam_shared_object_listener.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CCollectionCraftingSelectionPanel : public CItemCriteriaSelectionPanel
+{
+ DECLARE_CLASS_SIMPLE( CCollectionCraftingSelectionPanel, CItemCriteriaSelectionPanel );
+public:
+ CCollectionCraftingSelectionPanel( Panel *pParent ) : BaseClass( pParent, NULL ) {}
+
+ void SetCorrespondingItems( CCopyableUtlVector< const CEconItemView* >& vecSelectedItems )
+ {
+ m_vecCorrespondingItems = vecSelectedItems;
+ }
+
+ void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ vgui::Label* pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName( "ItemSlotLabel" ) );
+ if ( pWeaponLabel )
+ {
+ pWeaponLabel->SetVisible( false );
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ virtual const char *GetSelectionInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) const
+ {
+ return GetCollectionCraftingInvalidReason( pTestItem, pSourceItem );
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const
+ {
+ if ( !pItem )
+ return NULL;
+
+ const CEconItemView* pSourceItem = m_vecCorrespondingItems.Count() ? m_vecCorrespondingItems[0] : NULL;
+
+ FOR_EACH_VEC( m_vecCorrespondingItems, i )
+ {
+ if ( pItem->GetItemID() == m_vecCorrespondingItems[i]->GetItemID() )
+ {
+ return "#TF_StrangeCount_Transfer_Self";
+ }
+ }
+
+ return GetSelectionInvalidReason( pItem, pSourceItem );
+ }
+
+ virtual bool ShouldDeleteOnClose( void ) OVERRIDE{ return false; }
+
+protected:
+ const char * m_pszTitleToken;
+ CUtlVector< const CEconItemView* > m_vecCorrespondingItems;
+};
+
+//-----------------------------------------------------------------------------
+// A panel to let users choose 10 weapons to craft up within collections
+//-----------------------------------------------------------------------------
+class CCollectionCraftingPanel : public vgui::EditablePanel, public CGameEventListener, public CLocalSteamSharedObjectListener
+{
+public:
+ DECLARE_CLASS_SIMPLE( CCollectionCraftingPanel, vgui::EditablePanel );
+ CCollectionCraftingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip );
+ ~CCollectionCraftingPanel( void );
+
+ virtual const char *GetResFile( void ) { return "Resource/UI/econ/CollectionCraftingDialog.res"; }
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void SetVisible( bool bVisible ) OVERRIDE;
+
+ virtual void SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE;
+
+ virtual void Show( CUtlVector< const CEconItemView* >& vecStartingItems );
+ void SetWaitingForItem( eEconItemOrigin eOrigin );
+
+ virtual int GetInputItemCount() { return COLLECTION_CRAFTING_ITEM_COUNT; }
+ virtual int GetOutputItemCount() { return 0; } // For Ui Display Purposes
+
+ MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel );
+ MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data );
+
+protected:
+
+ virtual void SetItemPanelCount( );
+ virtual void CreateSelectionPanel();
+ virtual void CreateItemPanels();
+
+ void SelectPanel( int nPanel );
+ void UpdateOKButton();
+ void SetItem( const CEconItemView* pItem, int nIndex );
+ virtual void OnThink() OVERRIDE;
+
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+
+ DHANDLE<CCollectionCraftingSelectionPanel> m_hSelectionPanel;
+
+ CExButton *m_pOKButton;
+ CExButton *m_pNextItemButton;
+
+ EditablePanel* m_pTradeUpContainer;
+ CItemModelPanel* m_pSelectingItemModelPanel;
+ CUtlVector< EditablePanel* > m_vecItemContainers;
+ CUtlVector< ImagePanel* > m_vecImagePanels;
+ CUtlVector< CItemModelPanel* > m_vecItemPanels;
+
+ CUtlVector< EditablePanel* > m_vecOutputItemContainers;
+ CUtlVector< ImagePanel* > m_vecOutputImagePanels;
+ CUtlVector< CItemModelPanel* > m_vecOutputItemPanels;
+
+ CUtlVector< CUtlString > m_vecBoxTopNames;
+ CUtlVector< CUtlString > m_vecStampNames;
+ CUtlVector< CUtlString > m_vecResultStrings;
+ struct LocalizedPanelAction_t
+ {
+ CUtlString m_strPanel;
+ bool m_bShowForEnglish;
+ };
+ CUtlVector< LocalizedPanelAction_t > m_vecLocalizedPanels;
+ CBaseModelPanel *m_pModelPanel;
+ ImagePanel* m_pStampPanel;
+ CExButton* m_pStampButton;
+
+ CDrawingPanel *m_pDrawingPanel;
+ CTFItemInspectionPanel *m_pInspectPanel;
+ CItemModelPanel* m_pCosmeticResultItemModelPanel;
+ CItemModelPanel* m_pItemNamePanel;
+
+ KeyValues* m_pKVItemPanels;
+ bool m_bWaitingForGCResponse;
+ RealTimeCountdownTimer m_timerResponse;
+ CUtlVector<itemid_t> m_nFoundItemID;
+ bool m_bEnvelopeReadyToSend;
+ bool m_bShowing;
+ bool m_bShowImmediately;
+
+ eEconItemOrigin m_eEconItemOrigin;
+
+ CPanelAnimationVarAliasType( int, m_iButtonsStartX, "buttons_start_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iButtonsStartY, "buttons_start_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iButtonsStepX, "buttons_step_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iButtonsStepY, "buttons_step_y", "0", "proportional_int" );
+
+ CPanelAnimationVarAliasType( int, m_iOutputItemStartX, "output_start_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iOutputItemStartY, "output_start_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iOutputItemStepX, "output_step_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iOutputItemStepY, "output_step_y", "0", "proportional_int" );
+
+ CPanelAnimationVarAliasType( float, m_flSlideInTime, "slide_in_time", "1.0", "float" );
+ CPanelAnimationVarAliasType( int, m_iBGContainerTargetY, "bg_target_y", "0", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStatClockCraftingSelectionPanel : public CCollectionCraftingSelectionPanel
+{
+ DECLARE_CLASS_SIMPLE( CStatClockCraftingSelectionPanel, CCollectionCraftingSelectionPanel );
+public:
+ CStatClockCraftingSelectionPanel( Panel *pParent ) : BaseClass( pParent ) {}
+
+ //-----------------------------------------------------------------------------
+ virtual const char *GetSelectionInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) const
+ {
+ return GetCraftCommonStatClockInvalidReason( pTestItem, pSourceItem ); // FIX ME
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// A panel to let users choose 10 weapons to craft up within collections
+//-----------------------------------------------------------------------------
+class CCraftCommonStatClockPanel : public CCollectionCraftingPanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CCraftCommonStatClockPanel, CCollectionCraftingPanel );
+ CCraftCommonStatClockPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip );
+ ~CCraftCommonStatClockPanel( void );
+
+ virtual const char *GetResFile( void ) { return "Resource/UI/econ/MannCoTrade_CommonStatClock.res"; }
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ virtual int GetInputItemCount() { return CRAFT_COMMON_STATCLOCK_ITEM_COUNT; }
+ virtual int GetOutputItemCount() { return 1; }
+
+ virtual void Show( CUtlVector< const CEconItemView* >& vecStartingItems );
+
+protected:
+
+ virtual void CreateSelectionPanel();
+
+ CEconItemView m_outputItem;
+};
+
+#endif // COLLECTION_CRAFTING_PANEL_H
diff --git a/game/client/tf/vgui/crafting_panel.cpp b/game/client/tf/vgui/crafting_panel.cpp
new file mode 100644
index 0000000..9a503d7
--- /dev/null
+++ b/game/client/tf/vgui/crafting_panel.cpp
@@ -0,0 +1,1738 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "crafting_panel.h"
+#include "vgui/ISurface.h"
+#include "vgui/ISystem.h"
+#include "c_tf_player.h"
+#include "gamestringpool.h"
+#include "iclientmode.h"
+#include "tf_item_inventory.h"
+#include "ienginevgui.h"
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/CheckButton.h"
+#include "vgui_controls/ComboBox.h"
+#include <vgui_controls/TextEntry.h>
+#include "vgui/IInput.h"
+#include "gcsdk/gcclient.h"
+#include "gcsdk/gcclientjob.h"
+#include "character_info_panel.h"
+#include "charinfo_loadout_subpanel.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "tf_hud_notification_panel.h"
+#include "tf_hud_chat.h"
+#include "c_tf_gamestats.h"
+#include "confirm_dialog.h"
+#include "econ_notifications.h"
+#include "gc_clientsystem.h"
+#include "charinfo_loadout_subpanel.h"
+#include "item_selection_criteria.h"
+#include "rtime.h"
+#include "c_tf_freeaccount.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+ConVar tf_explanations_craftingpanel( "tf_explanations_craftingpanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." );
+
+struct recipefilter_data_t
+{
+ const char *pszTooltipString;
+ const char *pszButtonImage;
+ const char *pszButtonImageMouseover;
+};
+recipefilter_data_t g_RecipeFilters[NUM_RECIPE_CATEGORIES] =
+{
+ { "#RecipeFilter_Crafting", "crafticon_crafting_items", "crafticon_crafting_items_over" }, // RECIPE_CATEGORY_CRAFTINGITEMS,
+ { "#RecipeFilter_CommonItems", "crafticon_common_items", "crafticon_common_items_over" }, // RECIPE_CATEGORY_COMMONITEMS,
+ { "#RecipeFilter_RareItems", "crafticon_rare_items", "crafticon_rare_items_over" }, // RECIPE_CATEGORY_RAREITEMS,
+ { "#RecipeFilter_Special", "crafticon_special_blueprints", "crafticon_special_blueprints_over" } // RECIPE_CATEGORY_SPECIAL,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+wchar_t *LocalizeRecipeStringPiece( const char *pszString, wchar_t *pszConverted, int nConvertedSizeInBytes )
+{
+ if ( !pszString )
+ return L"";
+
+ if ( pszString[0] == '#' )
+ return g_pVGuiLocalize->Find( pszString );
+
+ g_pVGuiLocalize->ConvertANSIToUnicode( pszString, pszConverted, nConvertedSizeInBytes );
+ return pszConverted;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void SetItemPanelToRecipe( CItemModelPanel *pPanel, const CEconCraftingRecipeDefinition *pRecipeDef, bool bShowName )
+{
+ wchar_t wcTmpName[512];
+ wchar_t wcTmpDesc[512];
+ int iNegAttribsBegin = 0;
+
+ if ( !pRecipeDef )
+ {
+ Q_wcsncpy( wcTmpName, g_pVGuiLocalize->Find( "#Craft_Recipe_Custom" ), sizeof( wcTmpName ) );
+ Q_wcsncpy( wcTmpDesc, g_pVGuiLocalize->Find( "#Craft_Recipe_CustomDesc" ), sizeof( wcTmpDesc ) );
+ iNegAttribsBegin = Q_wcslen( wcTmpDesc );
+ }
+ else
+ {
+ if ( bShowName )
+ {
+ wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() );
+ g_pVGuiLocalize->ConstructString_safe( wcTmpName, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A );
+ }
+ else
+ {
+ wcTmpName[0] = '\0';
+ }
+
+ wchar_t wcTmpA[32];
+ wchar_t wcTmpB[32];
+ wchar_t wcTmpC[32];
+ wchar_t wcTmp[512];
+
+ // Build the input string
+ wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) );
+ wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) );
+ wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) );
+ g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C );
+ iNegAttribsBegin = Q_wcslen(wcTmpDesc);
+
+ // Build the output string
+ wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) );
+ wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) );
+ wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) );
+ g_pVGuiLocalize->ConstructString_safe( wcTmp, g_pVGuiLocalize->Find( pRecipeDef->GetDescOutputs() ), 3, pOut_A, pOut_B, pOut_C );
+
+ // Concatenate, and mark the text changes
+ V_wcscat_safe( wcTmpDesc, L"\n" );
+ V_wcscat_safe( wcTmpDesc, wcTmp );
+ }
+
+ pPanel->SetAttribOnly( !bShowName );
+ pPanel->SetTextYPos( 0 );
+ pPanel->SetItem( NULL );
+ pPanel->SetNoItemText( wcTmpName, wcTmpDesc, iNegAttribsBegin );
+ pPanel->InvalidateLayout(true);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void PositionMouseOverPanelForRecipe( vgui::Panel *pScissorPanel, vgui::Panel *pRecipePanel, vgui::ScrollableEditablePanel *pRecipeScroller, CItemModelPanel *pMouseOverItemPanel )
+{
+ int x,y;
+ vgui::ipanel()->GetAbsPos( pRecipePanel->GetVPanel(), x, y );
+ int xs,ys;
+ vgui::ipanel()->GetAbsPos( pMouseOverItemPanel->GetParent()->GetVPanel(), xs, ys );
+ x -= xs;
+ y -= ys;
+
+ int iXPos = (x + (pRecipePanel->GetWide() * 0.5)) - (pMouseOverItemPanel->GetWide() * 0.5);
+ int iYPos = (y + pRecipePanel->GetTall());
+
+ // Make sure the popup stays onscreen.
+ if ( iXPos < 0 )
+ {
+ iXPos = 0;
+ }
+ else if ( (iXPos + pMouseOverItemPanel->GetWide()) > pMouseOverItemPanel->GetParent()->GetWide() )
+ {
+ iXPos = pMouseOverItemPanel->GetParent()->GetWide() - pMouseOverItemPanel->GetWide();
+ }
+
+ if ( iYPos < 0 )
+ {
+ iYPos = 0;
+ }
+ else if ( (iYPos + pMouseOverItemPanel->GetTall() + YRES(32)) > pMouseOverItemPanel->GetParent()->GetTall() )
+ {
+ // Move it up above our item
+ iYPos = y - pMouseOverItemPanel->GetTall() - YRES(4);
+ }
+
+ pMouseOverItemPanel->SetPos( iXPos, iYPos );
+ pMouseOverItemPanel->SetVisible( true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCraftingPanel::CCraftingPanel( vgui::Panel *parent, const char *panelName ) : CBaseLoadoutPanel( parent, panelName )
+{
+ m_pRecipeListContainer = new vgui::EditablePanel( this, "recipecontainer" );
+ m_pRecipeListContainerScroller = new vgui::ScrollableEditablePanel( this, m_pRecipeListContainer, "recipecontainerscroller" );
+ m_pSelectedRecipeContainer = new vgui::EditablePanel( this, "selectedrecipecontainer" );
+ m_pRecipeButtonsKV = NULL;
+ m_pRecipeFilterButtonsKV = NULL;
+ m_bEventLogging = false;
+ m_iCraftingAttempts = 0;
+ m_iRecipeCategoryFilter = RECIPE_CATEGORY_CRAFTINGITEMS;
+ m_iCurrentlySelectedRecipe = -1;
+ CleanupPostCraft( true );
+
+ m_pToolTip = new CTFTextToolTip( this );
+ m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" );
+ m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
+ m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
+ m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel );
+ m_pToolTip->SetTooltipDelay( 0 );
+
+ m_pSelectionPanel = NULL;
+ m_iSelectingForSlot = 0;
+
+ m_pCraftButton = NULL;
+ m_pUpgradeButton = NULL;
+ m_pFreeAccountLabel = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCraftingPanel::~CCraftingPanel( void )
+{
+ if ( m_pRecipeButtonsKV )
+ {
+ m_pRecipeButtonsKV->deleteThis();
+ m_pRecipeButtonsKV = NULL;
+ }
+ if ( m_pRecipeFilterButtonsKV )
+ {
+ m_pRecipeFilterButtonsKV->deleteThis();
+ m_pRecipeFilterButtonsKV = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( GetResFile() );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pRecipeListContainerScroller->GetScrollbar()->SetAutohideButtons( true );
+ m_pCraftButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("CraftButton") );
+ if ( m_pCraftButton )
+ {
+ m_pCraftButton->AddActionSignalTarget( this );
+ }
+ m_pUpgradeButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("UpgradeButton") );
+ if ( m_pUpgradeButton )
+ {
+ m_pUpgradeButton->AddActionSignalTarget( this );
+ }
+ m_pFreeAccountLabel = dynamic_cast<CExLabel*>( m_pSelectedRecipeContainer->FindChildByName("FreeAccountLabel") );
+
+ CreateRecipeFilterButtons();
+ UpdateRecipeFilter();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "recipebuttons_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pRecipeButtonsKV )
+ {
+ m_pRecipeButtonsKV->deleteThis();
+ }
+ m_pRecipeButtonsKV = new KeyValues("recipebuttons_kv");
+ pItemKV->CopySubkeys( m_pRecipeButtonsKV );
+ }
+
+ KeyValues *pButtonKV = inResourceData->FindKey( "recipefilterbuttons_kv" );
+ if ( pButtonKV )
+ {
+ if ( m_pRecipeFilterButtonsKV )
+ {
+ m_pRecipeFilterButtonsKV->deleteThis();
+ }
+ m_pRecipeFilterButtonsKV = new KeyValues("recipefilterbuttons_kv");
+ pButtonKV->CopySubkeys( m_pRecipeFilterButtonsKV );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // Need to lay these out before we start making item panels inside them
+ m_pRecipeListContainer->InvalidateLayout( true );
+ m_pRecipeListContainerScroller->InvalidateLayout( true );
+
+ // Position the recipe filters
+ FOR_EACH_VEC( m_pRecipeFilterButtons, i )
+ {
+ if ( m_pRecipeFilterButtonsKV )
+ {
+ m_pRecipeFilterButtons[i]->ApplySettings( m_pRecipeFilterButtonsKV );
+ m_pRecipeFilterButtons[i]->InvalidateLayout();
+ }
+
+ int iButtonW, iButtonH;
+ m_pRecipeFilterButtons[i]->GetSize( iButtonW, iButtonH );
+
+ int iXPos = (GetWide() * 0.5) + m_iFilterOffcenterX + ((iButtonW + m_iFilterDeltaX) * i);
+ int iYPos = m_iFilterYPos;// + ((iButtonH + m_iFilterDeltaY) * i);
+ m_pRecipeFilterButtons[i]->SetPos( iXPos, iYPos );
+ }
+
+ // Position the recipe buttons
+ for ( int i = 0; i < m_pRecipeButtons.Count(); i++ )
+ {
+ if ( m_pRecipeButtonsKV )
+ {
+ m_pRecipeButtons[i]->ApplySettings( m_pRecipeButtonsKV );
+ m_pRecipeButtons[i]->InvalidateLayout();
+ }
+
+ int iYDelta = m_pRecipeButtons[0]->GetTall() + YRES(2);
+
+ // Once we've setup our first item, we know how large to make the container
+ if ( i == 0 )
+ {
+ m_pRecipeListContainer->SetSize( m_pRecipeListContainer->GetWide(), iYDelta * m_pRecipeButtons.Count() );
+ }
+
+ int x,y;
+ m_pRecipeButtons[i]->GetPos( x,y );
+ m_pRecipeButtons[i]->SetPos( x, (iYDelta * i) );
+ }
+
+ // Now that the container has been sized, tell the scroller to re-evaluate
+ m_pRecipeListContainerScroller->InvalidateLayout();
+ m_pRecipeListContainerScroller->GetScrollbar()->InvalidateLayout();
+
+ // Then position all our item panels
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ PositionItemPanel( m_pItemModelPanels[i], i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::CreateRecipeFilterButtons( void )
+{
+ for ( int i = 0; i < NUM_RECIPE_CATEGORIES; i++ )
+ {
+ if ( m_pRecipeFilterButtons.Count() <= i )
+ {
+ CImageButton *pNewButton = new CImageButton( this, g_RecipeFilters[i].pszTooltipString );
+ m_pRecipeFilterButtons.AddToTail( pNewButton );
+ }
+
+ m_pRecipeFilterButtons[i]->SetInactiveImage( g_RecipeFilters[i].pszButtonImage );
+ m_pRecipeFilterButtons[i]->SetActiveImage( g_RecipeFilters[i].pszButtonImageMouseover );
+ m_pRecipeFilterButtons[i]->SetTooltip( m_pToolTip, g_RecipeFilters[i].pszTooltipString );
+ const char *pszCommand = VarArgs("selectfilter%d", i );
+ m_pRecipeFilterButtons[i]->SetCommand( pszCommand );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::UpdateRecipeFilter( void )
+{
+ int iMatchingRecipes = 0;
+ m_iCurrentlySelectedRecipe = -1;
+ m_iCurrentRecipeTotalInputs = 0;
+ m_iCurrentRecipeTotalOutputs = 0;
+
+ FOR_EACH_VEC( m_pRecipeFilterButtons, i )
+ {
+ bool bForceDepressed = ( i == m_iRecipeCategoryFilter );
+ m_pRecipeFilterButtons[i]->ForceDepressed( bForceDepressed );
+ }
+
+ // Loop through the known recipes, and see which ones match our category filter
+ for ( int i = 0; i < TFInventoryManager()->GetLocalTFInventory()->GetRecipeCount(); i++ )
+ {
+ const CEconCraftingRecipeDefinition *pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDef(i);
+ if ( !pRecipeDef )
+ continue;
+
+ if ( pRecipeDef->IsDisabled() )
+ continue;
+
+ if ( pRecipeDef->GetCategory() != m_iRecipeCategoryFilter )
+ continue;
+
+ wchar_t wTemp[256];
+ wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() );
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A );
+ SetButtonToRecipe( iMatchingRecipes, pRecipeDef->GetDefinitionIndex(), wTemp );
+
+ iMatchingRecipes++;
+ }
+
+ // Add a "Custom" option to the bottom of the Special recipe list
+ if ( m_iRecipeCategoryFilter == RECIPE_CATEGORY_SPECIAL )
+ {
+ SetButtonToRecipe( iMatchingRecipes, RECIPE_CUSTOM, g_pVGuiLocalize->Find("#Craft_Recipe_Custom") );
+ iMatchingRecipes++;
+ }
+
+ // Delete excess buttons
+ for ( int i = m_pRecipeButtons.Count() - 1; i >= iMatchingRecipes; i-- )
+ {
+ m_pRecipeButtons[i]->MarkForDeletion();
+ m_pRecipeButtons.Remove( i );
+ }
+
+ // Move the scrollbar to the top
+ m_pRecipeListContainerScroller->GetScrollbar()->SetValue( 0 );
+
+ UpdateSelectedRecipe( true );
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnCancelSelection( void )
+{
+ if ( m_pSelectionPanel )
+ {
+ m_pSelectionPanel->SetVisible( false );
+ }
+
+ CloseCraftingStatusDialog();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnSelectionReturned( KeyValues *data )
+{
+ if ( data )
+ {
+ uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );
+ if ( ulIndex == INVALID_ITEM_ID )
+ {
+ // should this be INVALID_ITEM_ID?
+ m_InputItems[m_iSelectingForSlot] = 0;
+ }
+ else
+ {
+ m_InputItems[m_iSelectingForSlot] = ulIndex;
+ }
+
+ UpdateModelPanels();
+ UpdateCraftButton();
+ }
+
+ // It'll have deleted itself, so we don't need to clean it up
+ OnCancelSelection();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
+{
+ if ( bVisible )
+ {
+ if ( m_pSelectionPanel )
+ {
+ m_pSelectionPanel->SetVisible( false );
+ }
+
+ memset( m_InputItems, 0, sizeof(m_InputItems) );
+ memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) );
+ m_iCurrentlySelectedRecipe = -1;
+ m_iCurrentRecipeTotalInputs = 0;
+ m_iCurrentRecipeTotalOutputs = 0;
+ UpdateRecipeFilter();
+
+ if ( !m_bEventLogging )
+ {
+ m_bEventLogging = true;
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ENTERED );
+ }
+ }
+ else
+ {
+ CloseCraftingStatusDialog();
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+ BaseClass::OnShowPanel( bVisible, bReturningFromArmory );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnClosing()
+{
+ if ( m_bEventLogging )
+ {
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_EXITED );
+ m_bEventLogging = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ int iCenter = 0;
+ int iButtonX, iButtonY, iXPos, iYPos;
+
+ if ( IsInputItemPanel(iIndex) )
+ {
+ iButtonX = (iIndex % CRAFTING_SLOTS_INPUT_COLUMNS);
+ iButtonY = (iIndex / CRAFTING_SLOTS_INPUT_COLUMNS);
+ iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+ }
+ else
+ {
+ int iButtonIndex = iIndex - CRAFTING_SLOTS_INPUTPANELS;
+ iButtonX = (iButtonIndex % CRAFTING_SLOTS_OUTPUT_COLUMNS);
+ iButtonY = (iButtonIndex / CRAFTING_SLOTS_OUTPUT_COLUMNS);
+ iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ iYPos = m_iOutputItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+ }
+
+ m_pItemModelPanels[iIndex]->SetPos( iXPos, iYPos );
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::UpdateRecipeItems( bool bClearInputItems )
+{
+ if ( bClearInputItems )
+ {
+ memset( m_InputItems, 0, sizeof(m_InputItems) );
+ }
+
+ memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) );
+ m_iCurrentRecipeTotalInputs = 0;
+ m_iCurrentRecipeTotalOutputs = 0;
+
+ if ( m_iCurrentlySelectedRecipe == -1 )
+ return;
+
+ /*
+ // Build lists of items divided by class & loadout slot, so recipes can quickly test themselves
+ CUtlVector<CEconItem*> vecAllItems;
+ CUtlVector<CEconItem*> vecItemsByClass[ LOADOUT_COUNT ];
+ CUtlVector<CEconItem*> vecItemsBySlot[ LOADOUT_POSITION_COUNT ];
+
+ for ( int i = 1; i <= TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount(); i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemByBackpackPosition(i);
+ if ( pItemData && pItemData->IsValid() )
+ {
+ CEconItem *pSOCData = pItemData->GetSOCData();
+ vecAllItems.AddToTail( pSOCData );
+
+ CTFItemDefinition *pItemDef = pItemData->GetStaticData();
+
+ // Put it in class lists for any class that can use it. Use the zeroth list as all-class items.
+ if ( pItemDef->CanBeUsedByAllClasses() )
+ {
+ vecItemsByClass[0].AddToTail( pSOCData );
+ }
+ for (int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( pItemDef->CanBeUsedByClass(iClass) )
+ {
+ vecItemsByClass[iClass].AddToTail( pSOCData );
+ }
+ }
+
+ // Put it in the slot lists for any slot that it can be equipped in
+ for (int iSlot = 0; iSlot < LOADOUT_POSITION_COUNT; iSlot++ )
+ {
+ if ( pItemDef->CanBePlacedInSlot( iSlot ) )
+ {
+ vecItemsBySlot[iSlot].AddToTail( pSOCData );
+ }
+ }
+ }
+ }
+ */
+
+ // Find the items needed for the specified recipe
+ if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
+ {
+ // Custom recipe. Show all open buttons, and let them put anything in there.
+ m_iCurrentRecipeTotalInputs = CRAFTING_SLOTS_INPUTPANELS;
+ m_iCurrentRecipeTotalOutputs = 0;
+
+ FOR_EACH_VEC( m_pItemModelPanels, i )
+ {
+ m_pItemModelPanels[i]->SetNoItemText( "" );
+ }
+ }
+ else
+ {
+ const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe );
+ if ( pRecipeDef )
+ {
+ m_iCurrentRecipeTotalInputs = pRecipeDef->GetTotalInputItemsRequired();
+ m_iCurrentRecipeTotalOutputs = pRecipeDef->GetTotalOutputItems();
+
+ CUtlVector<itemid_t> vecItemsUsed;
+
+ // Set the text in each of the item panels
+ const CUtlVector<CItemSelectionCriteria> *vecInputCriteria;
+ vecInputCriteria = pRecipeDef->GetInputItems();
+ CUtlVector<uint32> vecInputDupes;
+ vecInputDupes = pRecipeDef->GetInputItemDupeCounts();
+
+ int iModelPanel = 0;
+ FOR_EACH_VEC( *vecInputCriteria, i )
+ {
+ const char *pszNoItemText = GetItemTextForCriteria( &(*vecInputCriteria)[i] );
+
+ int iNumPanels = vecInputDupes[i] ? vecInputDupes[i] : 1;
+ for ( int iPanel = 0; iPanel < iNumPanels; iPanel++ )
+ {
+ m_ItemPanelCriteria[iModelPanel] = &(*vecInputCriteria)[i];
+ if ( m_pItemModelPanels[iModelPanel] )
+ {
+ m_pItemModelPanels[iModelPanel]->SetNoItemText( pszNoItemText );
+ }
+ iModelPanel++;
+ }
+ }
+
+ // Set the output items as well
+ CUtlVector<CItemSelectionCriteria> vecOutputCriteria;
+ vecOutputCriteria = pRecipeDef->GetOutputItems();
+ FOR_EACH_VEC( vecOutputCriteria, i )
+ {
+ int iOutputPanel = CRAFTING_SLOTS_INPUTPANELS + i;
+
+ CEconItemDefinition *pDef = GetItemDefFromCriteria( &vecOutputCriteria[i] );
+ if ( pDef )
+ {
+ //m_pItemModelPanels[iOutputPanel]->SetNoItemText( pszNoItemText );
+ CEconItemView *pItemData = new CEconItemView();
+ pItemData->Init( pDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ if ( m_pItemModelPanels[iOutputPanel] )
+ {
+ m_pItemModelPanels[iOutputPanel]->SetItem( pItemData );
+ }
+ delete pItemData;
+ continue;
+ }
+
+ // If we didn't manage to extract an output, just use the recipe output string
+ wchar_t wcTmpA[32];
+ wchar_t wcTmpB[32];
+ wchar_t wcTmpC[32];
+ wchar_t wcTmp[512];
+ wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) );
+ wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) );
+ wcTmp[0] = '\0';
+ V_wcscat_safe( wcTmp, pOut_A );
+ V_wcscat_safe( wcTmp, L" " );
+ V_wcscat_safe( wcTmp, pOut_B );
+ if ( Q_strnicmp( pRecipeDef->GetDescOutputs(), "#RDO_ABC", 8 ) == 0 )
+ {
+ wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) );
+ V_wcscat_safe( wcTmp, L" " );
+ V_wcscat_safe( wcTmp, pOut_C );
+ }
+
+ if ( m_pItemModelPanels[iOutputPanel] )
+ {
+ m_pItemModelPanels[iOutputPanel]->SetItem( NULL );
+ m_pItemModelPanels[iOutputPanel]->SetNoItemText( wcTmp );
+ }
+ }
+ }
+ }
+
+ // Now check to see if they've got the right items in there
+ UpdateCraftButton();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::UpdateCraftButton( void )
+{
+ if ( m_iCurrentlySelectedRecipe == -1 )
+ return;
+
+ bool bAllowedToUse = true;
+
+ const CEconCraftingRecipeDefinition *pRecipeDef = NULL;
+ if ( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM )
+ {
+ pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe );
+ if ( !pRecipeDef )
+ return;
+
+ bAllowedToUse = ( !IsFreeTrialAccount() || !pRecipeDef->IsPremiumAccountOnly() );
+ }
+
+ if ( m_pCraftButton )
+ {
+ m_pCraftButton->SetVisible( bAllowedToUse );
+ }
+ if ( m_pUpgradeButton )
+ {
+ m_pUpgradeButton->SetVisible( !bAllowedToUse );
+ }
+ if ( m_pFreeAccountLabel )
+ {
+ m_pFreeAccountLabel->SetVisible( !bAllowedToUse );
+ }
+
+ if ( !bAllowedToUse )
+ return;
+
+ bool bCraftButtonActive = false;
+ if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
+ {
+ // Need at least one item in a slot
+ for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
+ if ( pItemData )
+ {
+ bCraftButtonActive = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ CUtlVector<CEconItem*> vecAllItems;
+ for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
+ if ( pItemData )
+ {
+ vecAllItems.AddToTail( pItemData->GetSOCData() );
+ }
+ }
+
+ bCraftButtonActive = pRecipeDef->ItemListMatchesInputs( &vecAllItems, NULL, false, NULL );
+ }
+
+ if ( m_pCraftButton )
+ {
+ m_pCraftButton->SetEnabled( bCraftButtonActive );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CCraftingPanel::GetItemTextForCriteria( const CItemSelectionCriteria *pCriteria )
+{
+ // Otherwise, look at the first condition, and see if we can determine what the item is
+ const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ );
+ if ( pszVal && pszVal[0] )
+ {
+ // Is it a loadout slot?
+ int iSlot = StringFieldToInt( pszVal, ItemSystem()->GetItemSchema()->GetLoadoutStrings( EEquipType_t::EQUIP_TYPE_CLASS ), true );
+ if ( iSlot != -1 )
+ return ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[iSlot];
+
+ // Is it a craft material type?
+ if ( V_stricmp( pszVal, "weapon" ) == 0 )
+ {
+ return "#RI_W";
+ }
+ else if ( V_stricmp( pszVal, "hat" ) == 0 )
+ {
+ return "#RI_Hg";
+ }
+ else if ( V_stricmp( pszVal, "craft_token" ) == 0 )
+ {
+ return "#RI_T";
+ }
+ else if ( V_stricmp( pszVal, "class_token" ) == 0 )
+ {
+ return "#CI_T_C";
+ }
+ else if ( V_stricmp( pszVal, "slot_token" ) == 0 )
+ {
+ return "#CI_T_S";
+ }
+
+ // Is it an item name?
+ CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal);
+ if ( pDef )
+ return pDef->GetItemBaseName();
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconItemDefinition *CCraftingPanel::GetItemDefFromCriteria( const CItemSelectionCriteria *pCriteria )
+{
+ // Otherwise, look at the first condition, and see if we can determine what the item is
+ const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ );
+ if ( pszVal && pszVal[0] )
+ return ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal);
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::AddNewItemPanel( int iPanelIndex )
+{
+ BaseClass::AddNewItemPanel( iPanelIndex );
+
+ // Move the model panels to our selected recipe container
+ m_pItemModelPanels[iPanelIndex]->SetParent( m_pSelectedRecipeContainer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::UpdateModelPanels( void )
+{
+ BaseClass::UpdateModelPanels();
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( IsInputItemPanel(i) )
+ {
+ if ( m_InputItems[i] != 0 )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
+ m_pItemModelPanels[i]->SetItem( pItemData );
+ m_pItemModelPanels[i]->SetVisible( true );
+ m_pItemModelPanels[i]->SetShowEquipped( true );
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+ else
+ {
+ m_pItemModelPanels[i]->SetItem( NULL );
+
+ // Always show the number of slots that the recipe uses
+ bool bVisible = (m_iCurrentRecipeTotalInputs > i);
+ m_pItemModelPanels[i]->SetVisible( bVisible );
+ }
+ }
+ else
+ {
+ bool bVisible = ((m_iCurrentRecipeTotalOutputs + CRAFTING_SLOTS_INPUTPANELS) > i);
+ m_pItemModelPanels[i]->SetVisible( bVisible );
+ }
+ }
+
+ vgui::Panel *pLabel = m_pSelectedRecipeContainer->FindChildByName("OutputLabel");
+ if ( pLabel )
+ {
+ pLabel->SetVisible( m_iCurrentRecipeTotalOutputs > 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::SetButtonToRecipe( int iButton, int iDefIndex, wchar_t *pszText )
+{
+ // Re-use existing buttons, or make new ones if we need more
+ CRecipeButton *pRecipeButton = NULL;
+ if ( iButton < m_pRecipeButtons.Count() )
+ {
+ pRecipeButton = m_pRecipeButtons[iButton];
+ }
+ else
+ {
+ pRecipeButton = new CRecipeButton( m_pRecipeListContainer, "selectrecipe", "", this, "selectrecipe" );
+ if ( m_pRecipeButtonsKV )
+ {
+ pRecipeButton->ApplySettings( m_pRecipeButtonsKV );
+ }
+ pRecipeButton->MakeReadyForUse();
+ m_pRecipeButtons.AddToTail( pRecipeButton );
+ }
+
+ const char *pszCommand = VarArgs("selectrecipe%d", iDefIndex );
+ pRecipeButton->SetCommand( pszCommand );
+ pRecipeButton->SetText( pszText );
+ pRecipeButton->SetDefIndex( iDefIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::UpdateSelectedRecipe( bool bClearInputItems )
+{
+ for ( int i = 0; i < m_pRecipeButtons.Count(); i++ )
+ {
+ bool bSelected = m_pRecipeButtons[i]->m_iRecipeDefIndex == m_iCurrentlySelectedRecipe;
+ m_pRecipeButtons[i]->ForceDepressed( bSelected );
+ m_pRecipeButtons[i]->RecalculateDepressedState();
+
+ if ( bSelected )
+ {
+ wchar_t wszText[1024];
+ m_pRecipeButtons[i]->GetText( wszText, ARRAYSIZE( wszText ) );
+ m_pSelectedRecipeContainer->SetDialogVariable( "recipetitle", wszText );
+
+ if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
+ {
+ m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", g_pVGuiLocalize->Find("#Craft_Recipe_CustomDesc") );
+ }
+ else
+ {
+ const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe );
+ if ( pRecipeDef )
+ {
+ // Build the input string
+ wchar_t wcTmpA[32];
+ wchar_t wcTmpB[32];
+ wchar_t wcTmpC[32];
+ wchar_t wcTmpDesc[512];
+ wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) );
+ wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) );
+ wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) );
+ g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C );
+ m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", wcTmpDesc );
+ }
+ }
+ }
+ }
+
+ m_pSelectedRecipeContainer->SetVisible( m_iCurrentlySelectedRecipe != -1 );
+
+ UpdateRecipeItems( bClearInputItems );
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "selectrecipe", 12 ) )
+ {
+ const char *pszNum = command+12;
+ if ( pszNum && pszNum[0] )
+ {
+ m_iCurrentlySelectedRecipe = atoi(pszNum);
+ UpdateSelectedRecipe( true );
+ }
+
+ return;
+ }
+ if ( !Q_strnicmp( command, "selectfilter", 12 ) )
+ {
+ const char *pszNum = command+12;
+ if ( pszNum && pszNum[0] )
+ {
+ m_iRecipeCategoryFilter = (recipecategories_t)atoi(pszNum);
+ UpdateRecipeFilter();
+ }
+
+ return;
+ }
+ else if ( !Q_strnicmp( command, "back", 4 ) )
+ {
+ PostMessage( GetParent(), new KeyValues("CraftingClosed") );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "craft", 5 ) )
+ {
+ if ( CheckForUntradableItems() )
+ {
+ Craft();
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "upgrade" ) )
+ {
+ EconUI()->CloseEconUI();
+ EconUI()->OpenStorePanel( STOREPANEL_SHOW_UPGRADESTEPS, false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( true, true );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnRecipePanelEntered( vgui::Panel *panel )
+{
+ CRecipeButton *pRecipePanel = dynamic_cast < CRecipeButton * > ( panel );
+
+ if ( pRecipePanel && IsVisible() && !IsIgnoringItemPanelEnters() )
+ {
+ const CEconCraftingRecipeDefinition *pRecipeDef = NULL;
+ if ( pRecipePanel->m_iRecipeDefIndex != RECIPE_CUSTOM )
+ {
+ pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( pRecipePanel->m_iRecipeDefIndex );
+ }
+
+ SetItemPanelToRecipe( GetMouseOverPanel(), pRecipeDef, false );
+ PositionMouseOverPanelForRecipe( this, pRecipePanel, m_pRecipeListContainerScroller, GetMouseOverPanel() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnRecipePanelExited( vgui::Panel *panel )
+{
+ GetMouseOverPanel()->SetAttribOnly( false );
+ GetMouseOverPanel()->SetTextYPos( YRES(20) );
+ GetMouseOverPanel()->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CCraftingPanel::GetItemPanelIndex( CItemModelPanel *pItemPanel )
+{
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i] == pItemPanel )
+ return i;
+ }
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnItemPanelMousePressed( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() )
+ {
+ int iPos = GetItemPanelIndex(pItemPanel);
+ if ( IsInputItemPanel(iPos) )
+ {
+ m_iSelectingForSlot = iPos;
+
+ // Create it the first time around
+ if ( !m_pSelectionPanel )
+ {
+ m_pSelectionPanel = new CCraftingItemSelectionPanel( this );
+ }
+
+ if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
+ {
+ m_pSelectionPanel->UpdateOnShow( NULL, true, m_InputItems, ARRAYSIZE(m_InputItems) );
+ }
+ else
+ {
+ // Clicked on an item in the crafting area. Open up the selection panel.
+ m_pSelectionPanel->UpdateOnShow( m_ItemPanelCriteria[iPos], false, m_InputItems, ARRAYSIZE(m_InputItems) );
+ }
+
+ m_pSelectionPanel->ShowDuplicateCounts( true );
+ m_pSelectionPanel->ShowPanel( 0, true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void ConfirmCraft( bool bConfirmed, void* pContext )
+{
+ CCraftingPanel *pCraftingPanel = ( CCraftingPanel* )pContext;
+ if ( bConfirmed )
+ {
+ pCraftingPanel->Craft();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CCraftingPanel::CheckForUntradableItems( void )
+{
+ bool bHasUntradable = false;
+ for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
+ {
+ if ( m_InputItems[i] != 0 )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
+ if ( pItemData->IsTradable() == false )
+ {
+ bHasUntradable = true;
+ break;
+ }
+ }
+ }
+
+ if ( bHasUntradable )
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Craft_Untradable_Title", "#Craft_Untradable_Text", "#GameUI_OK", "#Cancel", &ConfirmCraft );
+ pDialog->SetContext( this );
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::Craft( void )
+{
+ // Build our list of items that we're trying to craft
+ ++m_iCraftingAttempts;
+ CUtlVector<itemid_t> vecCraftingItems;
+ for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
+ {
+ if ( m_InputItems[i] != 0 )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ATTEMPT, pItemData, m_iCraftingAttempts );
+ vecCraftingItems.AddToTail( m_InputItems[i] );
+ }
+ }
+
+ if ( !vecCraftingItems.Count() )
+ return;
+
+ GCSDK::CGCMsg<MsgGCCraft_t> msg( k_EMsgGCCraft );
+ msg.Body().m_nRecipeDefIndex = m_iCurrentlySelectedRecipe;
+ msg.Body().m_nItemCount = vecCraftingItems.Count();
+ for ( int i = 0; i < vecCraftingItems.Count(); i++ )
+ {
+ msg.AddUint64Data( vecCraftingItems[i] );
+ }
+ GCClientSystem()->BSendMessage( msg );
+
+ OpenCraftingStatusDialog( this, "#CraftUpdate_Start", true, false, false );
+
+ // Start ticking so we can give up waiting if we don't get a response from the GC
+ // We use the VGUI time, because we may not be in a game at all.
+ m_flAbortCraftingAt = vgui::system()->GetCurrentTime() + 10;
+ m_bWaitingForCraftItems = false;
+ m_iRecipeIndexTried = m_iCurrentlySelectedRecipe;
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnCraftResponse( EGCMsgResponse eResponse, CUtlVector<uint64> *vecCraftedIndices, int iRecipeUsed )
+{
+ switch ( eResponse )
+ {
+ case k_EGCMsgResponseNoMatch:
+ {
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_NO_RECIPE_MATCH, NULL, m_iCraftingAttempts );
+ CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
+ OpenCraftingStatusDialog( this, "#CraftUpdate_NoMatch", false, true, false );
+ }
+ break;
+
+ case k_EGCMsgResponseDenied:
+ {
+ // Craft denied.
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts );
+ CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
+ OpenCraftingStatusDialog( this, "#CraftUpdate_Denied", false, true, false );
+ }
+ break;
+
+ // We've got the list of items crafted. We save off the item list until our item cache has all the items.
+ case k_EGCMsgResponseOK:
+ {
+ // Start ticking, and wait until the cache contains all the items in the list.
+ m_bWaitingForCraftItems = true;
+ m_vecNewlyCraftedItems = *vecCraftedIndices;
+
+ if ( iRecipeUsed != m_iRecipeIndexTried && iRecipeUsed != -1 )
+ {
+ m_iNewRecipeIndex = iRecipeUsed;
+ }
+ }
+ break;
+
+ default:
+ {
+ // Craft failed in some way.
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts );
+ OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false );
+ CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
+ }
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::ShowCraftFinish( void )
+{
+ TFInventoryManager()->ShowItemsCrafted( &m_vecNewlyCraftedItems );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( IsVisible() )
+ {
+ if ( m_flAbortCraftingAt )
+ {
+ if ( m_flAbortCraftingAt < vgui::system()->GetCurrentTime() )
+ {
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_TIMEOUT, NULL, m_iCraftingAttempts );
+ CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
+ OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false );
+ return;
+ }
+ }
+
+ if ( m_bWaitingForCraftItems )
+ {
+ // If all the items in our newly crafted list are in the cache, we can show the pickup.
+ FOR_EACH_VEC_BACK( m_vecNewlyCraftedItems, i )
+ {
+ CEconItemView* pNewItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( m_vecNewlyCraftedItems[i] );
+ if ( pNewItem == NULL )
+ return;
+ C_CTF_GameStats.Event_Crafting( IE_CRAFTING_SUCCESS, pNewItem, m_iCraftingAttempts );
+ }
+
+ m_bWaitingForCraftItems = false;
+
+ // We have all the new items, show the pickup
+ OpenCraftingStatusDialog( this, "#CraftUpdate_Success", false, true, true );
+ CleanupPostCraft( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingPanel::CleanupPostCraft( bool bClearInputItems )
+{
+ m_flAbortCraftingAt = 0;
+ m_bWaitingForCraftItems = false;
+
+ UpdateSelectedRecipe( bClearInputItems );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ConVar *CCraftingPanel::GetExplanationConVar( void )
+{
+ return &tf_explanations_craftingpanel;
+}
+
+//================================================================================================================================
+// NOT CONNECTED TO STEAM WARNING DIALOG
+//================================================================================================================================
+static vgui::DHANDLE<CCraftingStatusDialog> g_CraftingStatusPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCraftingStatusDialog::CCraftingStatusDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "CraftingStatusDialog" )
+{
+ m_pRecipePanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "RecipeItemModelPanel" ) );
+ m_bShowNewRecipe = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingStatusDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( m_bShowNewRecipe )
+ {
+ LoadControlSettings( "resource/UI/NewRecipeFoundDialog.res" );
+ }
+ else
+ {
+ LoadControlSettings( "resource/UI/CraftingStatusDialog.res" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingStatusDialog::OnCommand( const char *command )
+{
+ bool bClose = false;
+
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ // If we were a success, show the player their new crafted items
+ if ( m_bShowOnExit )
+ {
+ if ( EconUI()->GetCraftingPanel() )
+ {
+ EconUI()->GetCraftingPanel()->ShowCraftFinish();
+ }
+
+ m_bShowOnExit = false;
+ }
+
+ bClose = true;
+ }
+ else if ( !Q_stricmp( command, "forceclose" ) )
+ {
+ bClose = true;
+ }
+
+ if ( bClose )
+ {
+ m_bShowOnExit = false;
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+
+ EconUI()->SetPreventClosure( false );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingStatusDialog::OnTick( void )
+{
+ if ( !m_bAnimateEllipses || !IsVisible() )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+ else
+ {
+ m_iNumEllipses = ((m_iNumEllipses+1) % 4);
+ }
+
+ switch ( m_iNumEllipses )
+ {
+ case 3: SetDialogVariable( "ellipses", L"..." ); break;
+ case 2: SetDialogVariable( "ellipses", L".." ); break;
+ case 1: SetDialogVariable( "ellipses", L"." ); break;
+ default: SetDialogVariable( "ellipses", L"" ); break;
+ }
+
+ BaseClass::OnTick();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingStatusDialog::UpdateSchemeForVersion( bool bRecipe )
+{
+ m_bShowNewRecipe = bRecipe;
+ InvalidateLayout( false, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingStatusDialog::ShowStatusUpdate( bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit )
+{
+ m_bShowNewRecipe = false;
+
+ CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pButton )
+ {
+ pButton->SetVisible( bAllowClose );
+ pButton->SetEnabled( bAllowClose );
+ }
+
+ m_bAnimateEllipses = bAnimateEllipses;
+ if ( m_bAnimateEllipses )
+ {
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 500 );
+ SetDialogVariable( "ellipses", L"" );
+ m_iNumEllipses = 0;
+ }
+ else
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ SetDialogVariable( "ellipses", L"" );
+ }
+
+ m_bShowOnExit = bShowOnExit;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void SetupCraftingStatusDialog( vgui::Panel *pParent )
+{
+ if (!g_CraftingStatusPanel.Get())
+ {
+ g_CraftingStatusPanel = vgui::SETUP_PANEL( new CCraftingStatusDialog( pParent, NULL ) );
+ }
+ g_CraftingStatusPanel->SetVisible( true );
+ g_CraftingStatusPanel->MakePopup();
+ g_CraftingStatusPanel->MoveToFront();
+ g_CraftingStatusPanel->SetKeyBoardInputEnabled(true);
+ g_CraftingStatusPanel->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_CraftingStatusPanel );
+
+ EconUI()->SetPreventClosure( true );
+}
+
+CCraftingStatusDialog *OpenCraftingStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit )
+{
+ SetupCraftingStatusDialog( pParent );
+ g_CraftingStatusPanel->UpdateSchemeForVersion( false );
+ g_CraftingStatusPanel->SetDialogVariable( "updatetext", g_pVGuiLocalize->Find( pszText ) );
+ g_CraftingStatusPanel->ShowStatusUpdate( bAnimateEllipses, bAllowClose, bShowOnExit );
+ return g_CraftingStatusPanel;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CloseCraftingStatusDialog( void )
+{
+ if ( g_CraftingStatusPanel )
+ {
+ g_CraftingStatusPanel->OnCommand( "forceclose" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the craft response
+//-----------------------------------------------------------------------------
+class CGCCraftResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCCraftResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ CUtlVector<uint64> vecCraftedIndices;
+ uint16 iItems = 0;
+ if ( !msg.BReadUint16Data( &iItems ) )
+ return true;
+ vecCraftedIndices.SetSize( iItems );
+ for ( int i = 0; i < iItems; i++ )
+ {
+ if( !msg.BReadUint64Data( &vecCraftedIndices[i] ) )
+ return true;
+ }
+
+ if ( EconUI()->GetCraftingPanel() )
+ {
+ EconUI()->GetCraftingPanel()->OnCraftResponse( (EGCMsgResponse)msg.Body().m_eResponse, &vecCraftedIndices, msg.Body().m_nResponseIndex );
+ }
+
+ //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse );
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCCraftResponse, "CGCCraftResponse", k_EMsgGCCraftResponse, GCSDK::k_EServerTypeGCClient );
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the Golden Wrench broadcast message
+//-----------------------------------------------------------------------------
+class CGCGoldenWrenchBroadcast : public GCSDK::CGCClientJob
+{
+public:
+ CGCGoldenWrenchBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgTFGoldenWrenchBroadcast> msg( pNetPacket );
+
+ // @todo Tom Bui: should we display this in some other manner? This gets covered up by the crafting panel.
+ CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
+ if ( pNotifyPanel )
+ {
+ bool bDeleted = msg.Body().deleted();
+ wchar_t szPlayerName[1024];
+ g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().user_name().c_str(), szPlayerName, sizeof(szPlayerName) );
+ wchar_t szWrenchNumber[16]=L"";
+ _snwprintf( szWrenchNumber, ARRAYSIZE( szWrenchNumber ), L"%i", msg.Body().wrench_number() );
+ wchar_t szNotification[1024]=L"";
+ g_pVGuiLocalize->ConstructString_safe( szNotification,
+ g_pVGuiLocalize->Find( bDeleted ? "#TF_HUD_Event_GoldenWrench_D": "#TF_HUD_Event_GoldenWrench_C" ),
+ 2, szPlayerName, szWrenchNumber );
+ pNotifyPanel->SetupNotifyCustom( szNotification, HUD_NOTIFY_GOLDEN_WRENCH, 10.0f );
+
+ // echo to chat
+ CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
+ if ( pHUDChat )
+ {
+ char szAnsi[1024];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( szNotification, szAnsi, sizeof(szAnsi) );
+
+ pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi );
+ }
+
+ // play a sound
+ vgui::surface()->PlaySound( bDeleted ? "vo/announcer_failure.mp3" : "vo/announcer_success.mp3" );
+ }
+
+ //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse );
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCGoldenWrenchBroadcast, "CGCGoldenWrenchBroadcast", k_EMsgGCGoldenWrenchBroadcast, GCSDK::k_EServerTypeGCClient );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the Saxxy broadcast message
+//-----------------------------------------------------------------------------
+class CGSaxxyBroadcast : public GCSDK::CGCClientJob
+{
+public:
+ CGSaxxyBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgTFSaxxyBroadcast> msg( pNetPacket );
+
+ CEconNotification *pNotification = new CEconNotification();
+ pNotification->SetText( "#TF_Event_Saxxy_Deleted" );
+ pNotification->SetLifetime( 30.0f );
+
+ {
+ // Who deleted this?
+ wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ];
+ g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) );
+ pNotification->AddStringToken( "owner", wszPlayerName );
+
+ // What category was the Saxxy for?
+ char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH];
+ Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category_number() );
+
+ pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) );
+ }
+
+ NotificationQueue_Add( pNotification );
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGSaxxyBroadcast, "CGSaxxyBroadcast", k_EMsgGCSaxxyBroadcast, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive any generic item deletion notification
+//-----------------------------------------------------------------------------
+class CClientItemBroadcastNotificationJob : public GCSDK::CGCClientJob
+{
+public:
+ CClientItemBroadcastNotificationJob( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgGCTFSpecificItemBroadcast> msg( pNetPacket );
+
+ CEconNotification *pNotification = new CEconNotification();
+ pNotification->SetText( msg.Body().was_destruction() ? "#TF_Event_Item_Deleted" : "#TF_Event_Item_Created" );
+ pNotification->SetLifetime( 30.0f );
+
+ // Who deleted this?
+ wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ];
+ g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) );
+ pNotification->AddStringToken( "owner", wszPlayerName );
+
+ // What type of item was this?
+ const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( msg.Body().item_def_index() );
+ if ( pItemDef )
+ {
+ pNotification->AddStringToken( "item_name", g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ) );
+
+ NotificationQueue_Add( pNotification );
+ }
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CClientItemBroadcastNotificationJob, "CClientItemBroadcastNotificationJob", k_EMsgGCTFSpecificItemBroadcast, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the Saxxy Awarded broadcast message
+//-----------------------------------------------------------------------------
+class CGSaxxyAwardedBroadcast : public GCSDK::CGCClientJob
+{
+private:
+ // embedded notification for custom trigger
+ class CSaxxyAwardedNotification : public CEconNotification
+ {
+ public:
+ CSaxxyAwardedNotification()
+ {
+ SetSoundFilename( "vo/announcer_success.mp3" );
+ }
+
+ virtual EType NotificationType() { return eType_Trigger; }
+
+ virtual void Trigger()
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.teamfortress.com/saxxyawards/winners.php" );
+ }
+ MarkForDeletion();
+ }
+ };
+
+public:
+
+ CGSaxxyAwardedBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg< CMsgSaxxyAwarded > msg( pNetPacket );
+
+ CEconNotification *pNotification = new CSaxxyAwardedNotification();
+ pNotification->SetText( "#TF_Event_Saxxy_Awarded" );
+ pNotification->SetLifetime( 30.0f );
+
+ {
+ // Winners
+ CFmtStr1024 strWinners;
+ for ( int i = 0; i < msg.Body().winner_names_size(); ++i )
+ {
+ strWinners.Append( msg.Body().winner_names( i ).c_str() );
+ if ( i + 1 < msg.Body().winner_names_size() )
+ {
+ strWinners.Append( "\n" );
+ }
+ }
+ wchar_t wszPlayerNames[ 1024 ];
+ g_pVGuiLocalize->ConvertANSIToUnicode( strWinners.Access(), wszPlayerNames, sizeof( wszPlayerNames ) );
+ pNotification->AddStringToken( "winners", wszPlayerNames );
+
+ // year
+ CRTime cTime;
+ cTime.SetToCurrentTime();
+ cTime.SetToGMT( false );
+ locchar_t wszYear[10];
+ loc_sprintf_safe( wszYear, LOCCHAR( "%04u" ), cTime.GetYear() );
+ pNotification->AddStringToken( "year", wszYear );
+
+ // What category was the Saxxy for?
+ char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH];
+ Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category() );
+ pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) );
+ }
+
+ NotificationQueue_Add( pNotification );
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGSaxxyAwardedBroadcast, "CGSaxxyAwardedBroadcast", k_EMsgGCSaxxy_Awarded, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive a generic system broadcast message
+//-----------------------------------------------------------------------------
+class CGCSystemMessageBroadcast : public GCSDK::CGCClientJob
+{
+public:
+ CGCSystemMessageBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
+ if ( !pHUDChat )
+ return false;
+
+ GCSDK::CProtoBufMsg<CMsgSystemBroadcast> msg( pNetPacket );
+
+ // retrieve the text
+ const char *pchMessage = msg.Body().message().c_str();
+ wchar_t *pwMessage = g_pVGuiLocalize->Find( pchMessage );
+ wchar_t wszConvertedText[2048] = L"";
+ if ( pwMessage == NULL )
+ {
+ g_pVGuiLocalize->ConvertANSIToUnicode( pchMessage, wszConvertedText, sizeof( wszConvertedText ) );
+ pwMessage = wszConvertedText;
+ }
+
+ Color color( 0xff, 0xcc, 0x33, 255 );
+ KeyValuesAD keyValues( "System Message" );
+ keyValues->SetWString( "message", pwMessage );
+ keyValues->SetColor( "custom_color", color );
+
+ // print to chat log
+ wchar_t wszLocalizedString[2048] = L"";
+ g_pVGuiLocalize->ConstructString_safe( wszLocalizedString, "#Notification_System_Message", keyValues );
+ pHUDChat->SetCustomColor( color );
+ pHUDChat->Printf( CHAT_FILTER_NONE, "%ls", wszLocalizedString );
+
+ // send to notification
+ CEconNotification* pNotification = new CEconNotification();
+ pNotification->SetText( "#Notification_System_Message" );
+ pNotification->SetKeyValues( keyValues );
+ pNotification->SetLifetime( 30.0f );
+ pNotification->SetSoundFilename( "ui/system_message_alert.wav" );
+ NotificationQueue_Add( pNotification );
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCSystemMessageBroadcast, "CGCSystemMessageBroadcast", k_EMsgGCSystemMessage, GCSDK::k_EServerTypeGCClient );
diff --git a/game/client/tf/vgui/crafting_panel.h b/game/client/tf/vgui/crafting_panel.h
new file mode 100644
index 0000000..5730615
--- /dev/null
+++ b/game/client/tf/vgui/crafting_panel.h
@@ -0,0 +1,202 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CRAFTING_PANEL_H
+#define CRAFTING_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "backpack_panel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "tf_gcmessages.h"
+#include "econ_gcmessages.h"
+#include "tf_imagepanel.h"
+#include "tf_controls.h"
+#include "item_selection_panel.h"
+
+class CImageButton;
+
+// Crafting slots on crafting page
+#define CRAFTING_SLOTS_INPUT_ROWS 3
+#define CRAFTING_SLOTS_INPUT_COLUMNS 4
+#define CRAFTING_SLOTS_INPUTPANELS (CRAFTING_SLOTS_INPUT_ROWS * CRAFTING_SLOTS_INPUT_COLUMNS)
+#define CRAFTING_SLOTS_OUTPUT_ROWS 1
+#define CRAFTING_SLOTS_OUTPUT_COLUMNS 4
+#define CRAFTING_SLOTS_COUNT (CRAFTING_SLOTS_INPUTPANELS + (CRAFTING_SLOTS_OUTPUT_ROWS * CRAFTING_SLOTS_OUTPUT_COLUMNS))
+
+#define RECIPE_CUSTOM -2
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CRecipeButton : public CExButton
+{
+private:
+ DECLARE_CLASS_SIMPLE( CRecipeButton, CExButton );
+
+public:
+ CRecipeButton( vgui::Panel *parent, const char *name, const char *text, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL )
+ : CExButton( parent, name, text, pActionSignalTarget, cmd )
+ {
+ }
+
+ virtual void ApplySettings( KeyValues *inResourceData )
+ {
+ BaseClass::ApplySettings( inResourceData );
+ SetEnabled( m_iRecipeDefIndex != -1 );
+ }
+
+ void SetDefIndex( int iIndex )
+ {
+ m_iRecipeDefIndex = iIndex;
+ SetEnabled( m_iRecipeDefIndex != -1 );
+ }
+
+ void OnCursorEntered( void )
+ {
+ PostActionSignal( new KeyValues("RecipePanelEntered") );
+ BaseClass::OnCursorEntered();
+ }
+
+ void OnCursorExited( void )
+ {
+ PostActionSignal( new KeyValues("RecipePanelExited") );
+ BaseClass::OnCursorExited();
+ }
+
+
+public:
+ int m_iRecipeDefIndex;
+};
+
+//-----------------------------------------------------------------------------
+// An inventory screen that handles displaying the crafting screen
+//-----------------------------------------------------------------------------
+class CCraftingPanel : public CBaseLoadoutPanel
+{
+ DECLARE_CLASS_SIMPLE( CCraftingPanel, CBaseLoadoutPanel );
+public:
+ CCraftingPanel( vgui::Panel *parent, const char *panelName );
+ ~CCraftingPanel( void );
+
+ virtual const char *GetResFile( void ) { return "Resource/UI/CraftingPanel.res"; }
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void PerformLayout( void );
+ virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory );
+ virtual void OnCommand( const char *command );
+
+ void CreateRecipeFilterButtons( void );
+ void UpdateRecipeFilter( void );
+
+ virtual int GetNumItemPanels( void ) { return CRAFTING_SLOTS_COUNT; };
+
+ bool IsInputItemPanel( int iSlot ) { return (iSlot < CRAFTING_SLOTS_INPUTPANELS); }
+ virtual void PositionItemPanel( CItemModelPanel *pPanel, int iIndex );
+ int GetItemPanelIndex( CItemModelPanel *pItemPanel );
+
+ void UpdateSelectedRecipe( bool bClearInputItems );
+ void UpdateRecipeItems( bool bClearInputItems );
+ void UpdateCraftButton( void );
+
+ const char *GetItemTextForCriteria( const CItemSelectionCriteria *pCriteria );
+ CEconItemDefinition *GetItemDefFromCriteria( const CItemSelectionCriteria *pCriteria );
+ virtual void AddNewItemPanel( int iPanelIndex );
+ virtual void UpdateModelPanels( void );
+ void SetButtonToRecipe( int iButton, int iDefIndex, wchar_t *pszText );
+
+ bool CheckForUntradableItems( void );
+ void Craft( void );
+ void OnCraftResponse( EGCMsgResponse eResponse, CUtlVector<uint64> *vecCraftedIndices, int iRecipeUsed );
+ void ShowCraftFinish( void );
+ virtual void OnTick( void );
+ void CleanupPostCraft( bool bClearInputItems );
+
+ MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel );
+ MESSAGE_FUNC_PTR( OnRecipePanelEntered, "RecipePanelEntered", panel );
+ MESSAGE_FUNC_PTR( OnRecipePanelExited, "RecipePanelExited", panel );
+ MESSAGE_FUNC( OnCancelSelection, "CancelSelection" );
+ MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data );
+
+ MESSAGE_FUNC( OnClosing, "Closing" );
+
+ virtual ConVar *GetExplanationConVar( void );
+
+private:
+ // Items in the input model panels
+ itemid_t m_InputItems[CRAFTING_SLOTS_INPUTPANELS];
+ const CItemSelectionCriteria *m_ItemPanelCriteria[CRAFTING_SLOTS_INPUTPANELS];
+
+ CExButton *m_pCraftButton;
+ CExButton *m_pUpgradeButton;
+ CExLabel *m_pFreeAccountLabel;
+ vgui::EditablePanel *m_pRecipeListContainer;
+ vgui::ScrollableEditablePanel *m_pRecipeListContainerScroller;
+ vgui::EditablePanel *m_pSelectedRecipeContainer;
+
+ KeyValues *m_pRecipeButtonsKV;
+ CUtlVector<CRecipeButton*> m_pRecipeButtons;
+ KeyValues *m_pRecipeFilterButtonsKV;
+ CUtlVector<CImageButton*> m_pRecipeFilterButtons;
+
+ int m_iCurrentlySelectedRecipe;
+ int m_iCurrentRecipeTotalInputs;
+ int m_iCurrentRecipeTotalOutputs;
+ recipecategories_t m_iRecipeCategoryFilter;
+
+ CUtlVector<itemid_t> m_vecNewlyCraftedItems;
+ double m_flAbortCraftingAt;
+ bool m_bWaitingForCraftItems;
+ int m_iRecipeIndexTried;
+ int m_iNewRecipeIndex;
+ bool m_bEventLogging;
+ int m_iCraftingAttempts;
+
+ CTFTextToolTip *m_pToolTip;
+ vgui::EditablePanel *m_pToolTipEmbeddedPanel;
+
+ CCraftingItemSelectionPanel *m_pSelectionPanel;
+ int m_iSelectingForSlot;
+
+ CPanelAnimationVarAliasType( int, m_iItemCraftingOffcenterX, "item_crafting_offcenter_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iFilterOffcenterX, "filter_xoffset", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iFilterYPos, "filter_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iFilterDeltaX, "filter_xdelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iFilterDeltaY, "filter_ydelta", "0", "proportional_int" );
+
+ CPanelAnimationVarAliasType( int, m_iOutputItemYPos, "output_item_ypos", "0", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A dialog used to show the current state of a crafting request.
+//-----------------------------------------------------------------------------
+class CCraftingStatusDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CCraftingStatusDialog, vgui::EditablePanel );
+
+public:
+ CCraftingStatusDialog( vgui::Panel *pParent, const char *pElementName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void OnCommand( const char *command );
+ virtual void OnTick( void );
+ void UpdateSchemeForVersion( bool bRecipe );
+ void ShowStatusUpdate( bool bAnimateEllipses, bool bAllowed, bool bShowOnExit );
+
+private:
+ bool m_bShowOnExit;
+ bool m_bAnimateEllipses;
+ int m_iNumEllipses;
+ bool m_bShowNewRecipe;
+ CItemModelPanel *m_pRecipePanel;
+};
+CCraftingStatusDialog *OpenCraftingStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit );
+CCraftingStatusDialog *OpenNewRecipeFoundDialog( vgui::Panel *pParent, const CEconCraftingRecipeDefinition *pRecipeDef );
+void CloseCraftingStatusDialog( void );
+
+#endif // CRAFTING_PANEL_H
diff --git a/game/client/tf/vgui/crate_detail_panels.cpp b/game/client/tf/vgui/crate_detail_panels.cpp
new file mode 100644
index 0000000..dcde85d
--- /dev/null
+++ b/game/client/tf/vgui/crate_detail_panels.cpp
@@ -0,0 +1,388 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "crate_detail_panels.h"
+#include "vgui_controls/TextImage.h"
+#include "econ_gcmessages.h"
+#include "gc_clientsystem.h"
+#include "econ_ui.h"
+#include <vgui/ISurface.h>
+#include "econ_item_inventory.h"
+#include "econ/tool_items/tool_items.h"
+
+#define SHUFFLE_TIME 5.f
+
+float CInputStringForItemBackpackOverlayDialog::m_sflNextShuffleTime = 0.f;
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CInputStringForItemBackpackOverlayDialog::CInputStringForItemBackpackOverlayDialog( vgui::Panel *pParent, CEconItemView *pItem, CEconItemView *pChosenKey )
+ : vgui::EditablePanel( pParent, "InputStringForItemBackpackOverlayDialog" )
+ , m_Item( *pItem )
+ , m_pPreviewModelPanel( NULL )
+ , m_pTextEntry( NULL )
+ , m_pItemModelPanelKVs( NULL )
+ , m_bUpdateRecieved( false )
+{
+ if ( pChosenKey )
+ {
+ m_UseableKey = *pChosenKey;
+ }
+ m_pPreviewModelPanel = new CItemModelPanel( this, "preview_model" );
+ m_pTextEntry = new vgui::TextEntry( this, "TextEntryControl" );
+ m_pShuffleButton = new CExButton( this, "ShuffleButton", "Shuffle" );
+ m_pRareLootLabel = new CExLabel( this, "RareLootLabel", "#Econ_Revolving_Loot_List_Rare_Item" );
+ m_pProgressBar = new vgui::ProgressBar( this, "ShuffleProgress" );
+ m_pGetKeyButton = new CExButton( this, "GetKeyButton", "getkey" );
+ m_pUseKeyButton = new CExButton( this, "UseKeyButton", "usekey" );
+
+ m_pMouseOverItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mouseoveritempanel" ) );
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+
+ ListenForGameEvent( "inventory_updated" );
+}
+
+CInputStringForItemBackpackOverlayDialog::~CInputStringForItemBackpackOverlayDialog()
+{
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ m_pItemModelPanelKVs = NULL;
+ }
+
+ m_vecContentsPanels.PurgeAndDeleteElements();
+}
+
+void CInputStringForItemBackpackOverlayDialog::FireGameEvent( IGameEvent *event )
+{
+ // If we're not visible, ignore all events
+ if ( !IsVisible() )
+ return;
+
+ // Something caused our inventory to update. Assuming it was from our shuffle
+ // then we need to update ourselves.
+ const char *type = event->GetName();
+ if ( Q_strcmp( "inventory_updated", type ) == 0 )
+ {
+ m_bUpdateRecieved = true;
+ }
+}
+
+void CInputStringForItemBackpackOverlayDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/econ/InputStringForItemBackpackOverlayDialog.res" );
+
+ CCrateLootListWrapper itemWrapper( &m_Item );
+ const IEconLootList *pLootList = itemWrapper.GetEconLootList();
+ // Set the crate footer text. The crate itself specifies what to use.
+ if ( pLootList->GetLootListFooterLocalizationKey() )
+ {
+ m_pRareLootLabel->SetText( pLootList->GetLootListFooterLocalizationKey() );
+ }
+ else
+ {
+ const char *pszRareLootListFooterLocalizationKey = m_Item.GetItemDefinition()->GetDefinitionString( "loot_list_rare_item_footer", "#Econ_Revolving_Loot_List_Rare_Item" );
+ m_pRareLootLabel->SetText( pszRareLootListFooterLocalizationKey );
+ }
+
+ // Use the gradient border for the tooltip
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+
+ m_pPreviewModelPanel->SetItem( &m_Item );
+ m_pPreviewModelPanel->SetActAsButton( false, false ); // Dont mess around with the mouse
+
+ m_pTextEntry->RequestFocus();
+}
+
+void CInputStringForItemBackpackOverlayDialog::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ // Pull out the model panel KVs for this panel
+ KeyValues *pItemKV = inResourceData->FindKey( "modelpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ }
+ m_pItemModelPanelKVs = new KeyValues( "modelpanels_kv" );
+ pItemKV->CopySubkeys( m_pItemModelPanelKVs );
+ }
+
+ CreateItemPanels();
+}
+
+void CInputStringForItemBackpackOverlayDialog::CreateItemPanels()
+{
+ CCrateLootListWrapper itemWrapper( &m_Item );
+ const IEconLootList *pLootList = itemWrapper.GetEconLootList();
+
+ class CItemDefLootListIterator : public IEconLootList::IEconLootListIterator
+ {
+ public:
+ CItemDefLootListIterator( CUtlVector< item_definition_index_t > *pVecItemDefs )
+ : m_pVecItemDefs( pVecItemDefs )
+ {}
+
+ virtual void OnIterate( item_definition_index_t unItemDefIndex ) OVERRIDE
+ {
+ const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( unItemDefIndex );
+ if ( pItemDef && pItemDef->BValidForShuffle() )
+ {
+ m_pVecItemDefs->AddToTail( unItemDefIndex );
+ }
+ }
+
+ private:
+ CUtlVector< item_definition_index_t > * const m_pVecItemDefs;
+ };
+
+ // Get the drops from the item
+ CUtlVector< item_definition_index_t > vecItemDefs;
+ CItemDefLootListIterator it( &vecItemDefs );
+ pLootList->EnumerateUserFacingPotentialDrops( &it );
+
+ if ( !m_pItemModelPanelKVs )
+ return;
+
+ if ( m_vecContentsPanels.Count() != vecItemDefs.Count() )
+ {
+ m_vecContentsPanels.PurgeAndDeleteElements();
+
+ FOR_EACH_VEC( vecItemDefs, i )
+ {
+ // Create new panel
+ CItemModelPanel* pItemPanel = m_vecContentsPanels[ m_vecContentsPanels.AddToTail( new CItemModelPanel( this, CFmtStr( "item_preview_%d", i ) ) ) ];
+ pItemPanel->ApplySettings( m_pItemModelPanelKVs );
+ pItemPanel->InvalidateLayout( true );
+ pItemPanel->SetActAsButton( false, true ); // Lets us get mouse enter/exit evens for tooltips
+ pItemPanel->SetTooltip( m_pMouseOverTooltip, "" ); // Tooltip panel to use
+ }
+ }
+
+ // Create the panels and set the items into them
+ FOR_EACH_VEC( vecItemDefs, i )
+ {
+ const item_definition_index_t &itemDef = vecItemDefs[i];
+
+ CItemModelPanel* pItemPanel = m_vecContentsPanels[i];
+
+ CEconItemView item;
+ item.SetItemDefIndex( itemDef );
+ item.SetItemQuality( AE_UNIQUE ); // Unique by default
+ item.SetItemLevel( 0 ); // Hide this?
+ item.SetInitialized( true );
+ item.SetItemOriginOverride( kEconItemOrigin_Invalid );
+
+ pItemPanel->SetItem( &item );
+ }
+}
+
+void CInputStringForItemBackpackOverlayDialog::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // Find out how wide these panels will be side by side
+ const int nBuffer = 5;
+ int nTotalWide = 0;
+ const int nCount = m_vecContentsPanels.Count();
+ if ( nCount )
+ {
+ const int nWide = m_vecContentsPanels.Head()->GetWide();
+ nTotalWide = (nCount * nWide) + ( (nCount - 1) * nBuffer );
+ }
+
+ // Find out how much space the panels take up within the parent
+ int nParentWide = GetWide();
+ int nDiff = nParentWide - nTotalWide;
+ // How far we need to offset from the left edge
+ int nStartOffset = nDiff / 2;
+
+ // Place all the panels side by side
+ FOR_EACH_VEC( m_vecContentsPanels, i )
+ {
+ CItemModelPanel* pItemPanel = m_vecContentsPanels[ i ];
+
+ const int nWide = pItemPanel->GetWide();
+
+ pItemPanel->SetPos( nStartOffset + i * (nWide + nBuffer), YRES(150) );
+ pItemPanel->SetVisible( true );
+ }
+
+ // Which button to show
+ m_pUseKeyButton->SetVisible( m_UseableKey.IsValid() );
+ m_pGetKeyButton->SetVisible( !m_UseableKey.IsValid() );
+}
+
+void CInputStringForItemBackpackOverlayDialog::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "cancel", 6 ) )
+ {
+ TFModalStack()->PopModal( this );
+
+ SetVisible( false );
+ MarkForDeletion();
+ }
+ else if ( !Q_strnicmp( command, "shuffle", 7 ) )
+ {
+ // let the GC know
+ if ( m_pTextEntry && Plat_FloatTime() >= m_sflNextShuffleTime )
+ {
+ // Set the next time they can send a request to shuffle
+ m_sflNextShuffleTime = Plat_FloatTime() + SHUFFLE_TIME;
+
+ enum { kMaxCodeStringSize = 32 };
+ char szText[ kMaxCodeStringSize ] = { 0 };
+ m_pTextEntry->GetText( &szText[0], sizeof( szText ) );
+
+ GCSDK::CProtoBufMsg<CMsgGCShuffleCrateContents> msg( k_EMsgGCShuffleCrateContents );
+
+ msg.Body().set_crate_item_id( m_Item.GetID() );
+ msg.Body().set_user_code_string( szText );
+
+ GCClientSystem()->BSendMessage( msg );
+
+ m_pProgressBar->SetProgress( 0.f );
+ m_pProgressBar->SetVisible( true );
+ m_pTextEntry->SetVisible( false );
+
+ vgui::surface()->PlaySound( "ui/itemcrate_shuffle.wav" );
+ }
+ }
+ else if ( !Q_strnicmp( command, "getkey", 6 ) )
+ {
+ static CSchemaAttributeDefHandle pAttrDef_DecodedBy( "decoded by itemdefindex" );
+
+ uint32 iDecodableItemDef = 0;
+ if ( m_Item.FindAttribute( pAttrDef_DecodedBy, &iDecodableItemDef ) )
+ {
+ // casting to the proper type since our econ system is dumb
+ const float& value_as_float = (float&)iDecodableItemDef;
+ EconUI()->CloseEconUI();
+ EconUI()->OpenStorePanel( (int)value_as_float, false );
+
+ // close ourselves
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+ }
+ }
+ else if ( !Q_strnicmp( command, "usekey", 6 ) )
+ {
+ if ( m_UseableKey.IsValid() )
+ {
+ // Use the key
+ ApplyTool( GetParent(), &m_UseableKey, &m_Item );
+ // close ourselves
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+ }
+ }
+}
+
+void CInputStringForItemBackpackOverlayDialog::FindUsableKey()
+{
+ static CSchemaAttributeDefHandle pAttrDef_DecodedBy( "decoded by itemdefindex" );
+
+ uint32 iDecodableItemDef = 0;
+ if ( m_Item.FindAttribute( pAttrDef_DecodedBy, &iDecodableItemDef ) )
+ {
+ const float& value_as_float = (float&)iDecodableItemDef;
+ iDecodableItemDef = (float)value_as_float;
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( !pInventory )
+ return;
+
+ for ( int i = 0; i < pInventory->GetItemCount(); i++ )
+ {
+ CEconItemView *pItem = pInventory->GetItem(i);
+ if ( pItem->GetItemDefIndex() == iDecodableItemDef )
+ {
+ m_UseableKey = *pItem;
+ }
+ }
+ }
+}
+
+void CInputStringForItemBackpackOverlayDialog::OnThink()
+{
+ float flDelta = m_sflNextShuffleTime - Plat_FloatTime();
+
+ // If we're ready, show "Shuffle"
+ if ( flDelta < 0 )
+ {
+ // Show the text entry, show the progress bar
+ m_pProgressBar->SetVisible( false );
+ m_pTextEntry->SetVisible( true );
+
+ // Re-enable the shuffle/use buttons
+ m_pShuffleButton->SetEnabled( m_pTextEntry->GetTextLength() != 0 );
+ m_pUseKeyButton->SetEnabled( true );
+ // Say "Shuffle"
+ m_pShuffleButton->SetText( "#ShuffleContents" );
+
+ // We got a inventory update message, update
+ if ( m_bUpdateRecieved )
+ {
+ CreateItemPanels();
+ m_bUpdateRecieved = false;
+ }
+ }
+ else
+ {
+ // Show the progress bar, hide the text field
+ m_pProgressBar->SetVisible( true );
+ m_pTextEntry->SetVisible( false );
+
+ // Dont allow clicking the shuffle or use key button
+ m_pShuffleButton->SetEnabled( false );
+ m_pUseKeyButton->SetEnabled( false );
+ // Say "Shuffling..."
+ m_pShuffleButton->SetText( "#ShufflingContents" );
+
+ // Set progress
+ float flProgress = ( SHUFFLE_TIME - flDelta ) / SHUFFLE_TIME;
+ m_pProgressBar->SetProgress( flProgress );
+ }
+}
+
+void CInputStringForItemBackpackOverlayDialog::Show()
+{
+ SetVisible( true );
+ MakePopup();
+ MoveToFront();
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+ TFModalStack()->PushModal( this );
+
+ // If a key wasnt passed in, find the first one in the
+ // player's inventory
+ if ( !m_UseableKey.IsValid() )
+ {
+ FindUsableKey();
+ }
+
+ // Which button to show
+ m_pUseKeyButton->SetVisible( m_UseableKey.IsValid() );
+ m_pGetKeyButton->SetVisible( !m_UseableKey.IsValid() );
+
+ // Put the current gen code of the crate into the text field
+ static CSchemaAttributeDefHandle pAttrDef_DecodedBy( "crate generation code" );
+ const char *pszAttrGenCode;
+ if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( &m_Item, pAttrDef_DecodedBy, &pszAttrGenCode ) )
+ {
+ m_pTextEntry->SetText( pszAttrGenCode );
+ }
+}
+
+
diff --git a/game/client/tf/vgui/crate_detail_panels.h b/game/client/tf/vgui/crate_detail_panels.h
new file mode 100644
index 0000000..9d10c57
--- /dev/null
+++ b/game/client/tf/vgui/crate_detail_panels.h
@@ -0,0 +1,62 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CRATE_DETAIL_PANELS_H
+#define CRATE_DETAIL_PANELS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_controls.h"
+#include "item_model_panel.h"
+#include "econ_item_view.h"
+#include <vgui_controls/TextEntry.h>
+#include <vgui_controls/ProgressBar.h>
+
+class CInputStringForItemBackpackOverlayDialog : public vgui::EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CInputStringForItemBackpackOverlayDialog, vgui::EditablePanel );
+
+public:
+ CInputStringForItemBackpackOverlayDialog( vgui::Panel *pParent, CEconItemView *pItem, CEconItemView *pChosenKey = NULL );
+ ~CInputStringForItemBackpackOverlayDialog();
+
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+
+ void Show();
+
+protected:
+ CItemModelPanel *GetPreviewModelPanel() { return m_pPreviewModelPanel; }
+
+ CEconItemView m_Item;
+
+private:
+ void CreateItemPanels();
+ void FindUsableKey();
+
+ vgui::ProgressBar *m_pProgressBar;
+ CExLabel *m_pRareLootLabel;
+ CExButton *m_pUseKeyButton;
+ CExButton *m_pGetKeyButton;
+ CExButton *m_pShuffleButton;
+ CItemModelPanel *m_pPreviewModelPanel;
+ vgui::TextEntry *m_pTextEntry;
+ CUtlVector< CItemModelPanel* > m_vecContentsPanels;
+ KeyValues *m_pItemModelPanelKVs;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+ CItemModelPanel *m_pMouseOverItemPanel;
+ static float m_sflNextShuffleTime;
+ bool m_bUpdateRecieved;
+ CEconItemView m_UseableKey;
+};
+
+#endif // CRATE_DETAIL_PANELS_H
diff --git a/game/client/tf/vgui/drawing_panel.cpp b/game/client/tf/vgui/drawing_panel.cpp
new file mode 100644
index 0000000..f6e764a
--- /dev/null
+++ b/game/client/tf/vgui/drawing_panel.cpp
@@ -0,0 +1,335 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "drawing_panel.h"
+#include "softline.h"
+#include <vgui/IScheme.h>
+#include <vgui/IVGui.h>
+#include "voice_status.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern ConVar cl_mute_all_comms;
+
+Color g_DrawPanel_TeamColors[TF_TEAM_COUNT] =
+{
+ COLOR_TF_SPECTATOR, // unassigned
+ COLOR_TF_SPECTATOR, // spectator
+ COLOR_TF_RED, // red
+ COLOR_TF_BLUE, // blue
+};
+
+DECLARE_BUILD_FACTORY( CDrawingPanel );
+
+CDrawingPanel::CDrawingPanel( Panel *parent, const char*name ) : Panel( parent, name )
+{
+ m_bDrawingLines = false;
+ m_fLastMapLine = 0;
+ m_iMouseX = 0;
+ m_iMouseY = 0;
+ m_iPanelType = DRAWING_PANEL_TYPE_NONE;
+ m_bTeamColors = false;
+
+ m_nWhiteTexture = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile( m_nWhiteTexture, "vgui/white", true, false );
+
+ ListenForGameEvent( "cl_drawline" );
+}
+
+void CDrawingPanel::SetVisible( bool bState )
+{
+ ClearAllLines();
+
+ BaseClass::SetVisible( bState );
+}
+
+void CDrawingPanel::ReadColor( const char* pszToken, Color& color )
+{
+ if ( pszToken && *pszToken )
+ {
+ int r = 0, g = 0, b = 0, a = 255;
+ if ( sscanf( pszToken, "%d %d %d %d", &r, &g, &b, &a ) >= 3 )
+ {
+ // it's a direct color
+ color = Color( r, g, b, a );
+ }
+ else
+ {
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ color = pScheme->GetColor( pszToken, Color( 0, 0, 0, 0 ) );
+ }
+ }
+}
+
+void CDrawingPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ ReadColor( inResourceData->GetString( "linecolor", "" ), m_colorLine );
+ m_bTeamColors = inResourceData->GetBool( "team_colors", false );
+}
+
+void CDrawingPanel::Paint()
+{
+ for ( int iIndex = 0; iIndex < ARRAYSIZE( m_vecDrawnLines ); iIndex++ )
+ {
+ //Draw the lines
+ for ( int i = 0; i < m_vecDrawnLines[iIndex].Count(); i++ )
+ {
+ if ( !m_vecDrawnLines[iIndex][i].bSetBlipCentre )
+ {
+ Vector vecBlipPos;
+ vecBlipPos.x = m_vecDrawnLines[iIndex][i].worldpos.x;
+ vecBlipPos.y = m_vecDrawnLines[iIndex][i].worldpos.y;
+ vecBlipPos.z = 0;
+ //Msg("drawing line with blippos %f, %f\n", vecBlipPos.x, vecBlipPos.y);
+ m_vecDrawnLines[iIndex][i].blipcentre = m_vecDrawnLines[iIndex][i].worldpos;
+ //Msg(" which is blipcentre=%f, %f\n", m_MapLines[i].blipcentre.x, m_MapLines[i].blipcentre.y);
+ m_vecDrawnLines[iIndex][i].bSetBlipCentre = true;
+ }
+
+ float x = m_vecDrawnLines[iIndex][i].blipcentre.x;
+ float y = m_vecDrawnLines[iIndex][i].blipcentre.y;
+
+ if ( m_vecDrawnLines[iIndex][i].bLink )
+ {
+ if ( i > 1 )
+ {
+ m_vecDrawnLines[iIndex][i].linkpos = m_vecDrawnLines[iIndex][i-1].worldpos;
+
+ if ( !m_vecDrawnLines[iIndex][i].bSetLinkBlipCentre )
+ {
+ Vector vecBlipPos2;
+ vecBlipPos2.x = m_vecDrawnLines[iIndex][i].linkpos.x;
+ vecBlipPos2.y = m_vecDrawnLines[iIndex][i].linkpos.y;
+ vecBlipPos2.z = 0;
+ m_vecDrawnLines[iIndex][i].linkblipcentre = m_vecDrawnLines[iIndex][i].linkpos;
+ m_vecDrawnLines[iIndex][i].bSetLinkBlipCentre = true;
+ }
+
+ float x2 = m_vecDrawnLines[iIndex][i].linkblipcentre.x;
+ float y2 = m_vecDrawnLines[iIndex][i].linkblipcentre.y;
+
+ int alpha = 255;
+ if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY )
+ {
+ float t = gpGlobals->curtime - m_vecDrawnLines[iIndex][i].created_time;
+
+ if ( t < DRAWN_LINE_SOLID_TIME )
+ {
+
+ }
+ else if ( t < DRAWN_LINE_SOLID_TIME + DRAWN_LINE_FADE_TIME )
+ {
+ alpha = 255 - ( ( t - DRAWN_LINE_SOLID_TIME ) / DRAWN_LINE_FADE_TIME ) * 255.0f;
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ //Msg("drawing line from %f,%f to %f,%f\n", x, y, x2, y2);
+
+ vgui::surface()->DrawSetTexture( m_nWhiteTexture );
+ vgui::Vertex_t start, end;
+
+ Color drawColor = m_colorLine;
+ if ( m_bTeamColors )
+ {
+ C_BasePlayer *pPlayer = UTIL_PlayerByIndex( iIndex );
+ if ( pPlayer )
+ {
+ drawColor = g_DrawPanel_TeamColors[ pPlayer->GetTeamNumber() ];
+ }
+ }
+
+ // draw main line
+ vgui::surface()->DrawSetColor( Color( drawColor.r(), drawColor.g(), drawColor.b(), alpha ) );
+ start.Init( Vector2D( x, y ), Vector2D( 0, 0 ) );
+ end.Init( Vector2D( x2, y2 ), Vector2D( 1, 1 ) );
+ SoftLine::DrawPolygonLine( start, end );
+
+ // draw translucent ones around it to give it some softness
+ vgui::surface()->DrawSetColor( Color( drawColor.r(), drawColor.g(), drawColor.b(), 0.5f * alpha ) );
+
+ start.Init( Vector2D( x - 0.50f, y - 0.50f ), Vector2D( 0, 0 ) );
+ end.Init( Vector2D( x2 - 0.50f, y2 - 0.50f ), Vector2D( 1, 1 ) );
+ SoftLine::DrawPolygonLine( start, end );
+
+ start.Init( Vector2D( x + 0.50f, y - 0.50f ), Vector2D( 0, 0 ) );
+ end.Init( Vector2D( x2 + 0.50f, y2 - 0.50f ), Vector2D( 1, 1 ) );
+ SoftLine::DrawPolygonLine( start, end );
+
+ start.Init( Vector2D( x - 0.50f, y + 0.50f ), Vector2D( 0, 0 ) );
+ end.Init( Vector2D( x2 - 0.50f, y2 + 0.50f ), Vector2D( 1, 1 ) );
+ SoftLine::DrawPolygonLine( start, end );
+
+ start.Init( Vector2D( x + 0.50f, y + 0.50f ), Vector2D( 0, 0 ) );
+ end.Init( Vector2D( x2 + 0.50f, y2 + 0.50f ), Vector2D( 1, 1 ) );
+ SoftLine::DrawPolygonLine( start, end );
+ }
+ }
+ }
+ }
+}
+
+void CDrawingPanel::OnMousePressed( vgui::MouseCode code )
+{
+ if ( code != MOUSE_LEFT )
+ return;
+
+ SendMapLine( m_iMouseX, m_iMouseY, true );
+ m_bDrawingLines = true;
+}
+
+void CDrawingPanel::OnMouseReleased( vgui::MouseCode code )
+{
+ if ( code != MOUSE_LEFT )
+ return;
+
+ m_bDrawingLines = false;
+}
+
+void CDrawingPanel::OnCursorExited()
+{
+ //Msg("CDrawingPanel::OnCursorExited\n");
+ m_bDrawingLines = false;
+}
+
+void CDrawingPanel::OnThink()
+{
+ if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY )
+ {
+ // clean up any segments that have faded out
+ for ( int iIndex = 0; iIndex < ARRAYSIZE( m_vecDrawnLines ); iIndex++ )
+ {
+ for ( int i = m_vecDrawnLines[iIndex].Count() - 1; i >= 0; i-- )
+ {
+ if ( gpGlobals->curtime - m_vecDrawnLines[iIndex][i].created_time > DRAWN_LINE_SOLID_TIME + DRAWN_LINE_FADE_TIME )
+ {
+ m_vecDrawnLines[iIndex].Remove( i );
+ }
+ }
+ }
+ }
+}
+
+void CDrawingPanel::OnCursorMoved( int x, int y )
+{
+ //Msg("CDrawingPanel::OnCursorMoved %d,%d\n", x, y);
+ m_iMouseX = x;
+ m_iMouseY = y;
+
+ const float flLineInterval = 1.f / 60.f;
+
+ if ( m_bDrawingLines && gpGlobals->curtime >= m_fLastMapLine + flLineInterval )
+ {
+ SendMapLine( x, y, false );
+ }
+
+}
+
+void CDrawingPanel::SendMapLine( int x, int y, bool bInitial )
+{
+ if ( engine->IsPlayingDemo() )
+ return;
+
+ int iIndex = GetLocalPlayerIndex();
+ int nMaxLines = 750; // 12.5 seconds of drawing at 60fps
+ if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY )
+ {
+ nMaxLines = 120; // 2 seconds of drawing at 60fps
+ }
+
+ // Stop adding lines after this much
+ if ( m_vecDrawnLines[iIndex].Count() >= nMaxLines )
+ return;
+
+ int linetype = bInitial ? 0 : 1;
+
+ m_fLastMapLine = gpGlobals->curtime;
+
+ // short circuit add it to your own list
+ MapLine line;
+ line.worldpos.x = x;
+ line.worldpos.y = y;
+ line.created_time = gpGlobals->curtime;
+ if ( linetype == 1 ) // links to a previous
+ {
+ line.bLink = true;
+ }
+
+ m_vecDrawnLines[iIndex].AddToTail( line );
+
+ if ( m_iPanelType == DRAWING_PANEL_TYPE_MATCH_SUMMARY )
+ {
+ // notify the server of this!
+ KeyValues *kv = new KeyValues( "cl_drawline" );
+ kv->SetInt( "panel", m_iPanelType );
+ kv->SetInt( "line", linetype );
+ kv->SetFloat( "x", (float)x / (float)GetWide() );
+ kv->SetFloat( "y", (float)y / (float)GetTall() );
+ engine->ServerCmdKeyValues( kv );
+ }
+}
+
+void CDrawingPanel::ClearLines( int iIndex )
+{
+ m_vecDrawnLines[iIndex].Purge();
+}
+
+void CDrawingPanel::ClearAllLines()
+{
+ for ( int iIndex = 0; iIndex < ARRAYSIZE( m_vecDrawnLines ); iIndex++ )
+ {
+ m_vecDrawnLines[iIndex].Purge();
+ }
+}
+
+void CDrawingPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "cl_drawline" ) )
+ {
+ int iIndex = event->GetInt( "player" );
+
+ // if this is NOT the local player (we've already stored our own data)
+ if ( ( iIndex != GetLocalPlayerIndex() ) || engine->IsPlayingDemo() )
+ {
+ // If a player is muted for voice, also mute them for lines because jerks gonna jerk.
+ if ( cl_mute_all_comms.GetBool() && ( iIndex != 0 ) )
+ {
+ if ( GetClientVoiceMgr() && GetClientVoiceMgr()->IsPlayerBlocked( iIndex ) )
+ {
+ ClearLines( iIndex );
+ return;
+ }
+ }
+
+ int iPanelType = event->GetInt( "panel" );
+
+ // if this message is about our panel type
+ if ( iPanelType == m_iPanelType )
+ {
+ int iLineType = event->GetInt( "line" );
+ float x = event->GetFloat( "x" );
+ float y = event->GetFloat( "y" );
+
+ MapLine line;
+ line.worldpos.x = (int)( x * GetWide() );
+ line.worldpos.y = (int)( y * GetTall() );
+ line.created_time = gpGlobals->curtime;
+ if ( iLineType == 1 ) // links to a previous
+ {
+ line.bLink = true;
+ }
+
+ m_vecDrawnLines[iIndex].AddToTail( line );
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/drawing_panel.h b/game/client/tf/vgui/drawing_panel.h
new file mode 100644
index 0000000..3cfe991
--- /dev/null
+++ b/game/client/tf/vgui/drawing_panel.h
@@ -0,0 +1,84 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef DRAWING_PANEL_H
+#define DRAWING_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+
+#define DRAWN_LINE_SOLID_TIME 15.0f
+#define DRAWN_LINE_FADE_TIME 3.0f
+
+class MapLine
+{
+public:
+ MapLine()
+ {
+ worldpos.Init( 0, 0 );
+ linkpos.Init( 0, 0 );
+ created_time = 0;
+ bSetLinkBlipCentre = false;
+ bSetBlipCentre = false;
+ blipcentre.Init( 0, 0 );
+ linkblipcentre.Init( 0, 0 );
+ bLink = false;
+ }
+
+ Vector2D worldpos; // blip in world space
+ Vector2D blipcentre; // blip in map texture space
+ Vector2D linkpos; // link blip in world space
+ Vector2D linkblipcentre; // link blip in map texture space
+ bool bLink;
+ bool bSetBlipCentre; // have we calculated the blip in map texture space yet?
+ bool bSetLinkBlipCentre; // have we calculated the link blip in map texture space yet?
+ float created_time;
+};
+
+
+class CDrawingPanel : public vgui::Panel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CDrawingPanel, vgui::Panel );
+public:
+ CDrawingPanel( Panel *parent, const char *name );
+
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+
+ void SendMapLine( int x, int y, bool bInitial );
+ virtual void OnMouseReleased( vgui::MouseCode code );
+ virtual void OnMousePressed( vgui::MouseCode code );
+ virtual void OnCursorExited();
+ virtual void OnCursorMoved( int x, int y );
+ virtual void Paint();
+ virtual void OnThink();
+ virtual void SetVisible( bool bState ) OVERRIDE;
+ void ClearLines( int iIndex );
+ void ClearAllLines();
+ const CUtlVector<MapLine>& GetLines( int iIndex ) const { return m_vecDrawnLines[iIndex]; }
+ void SetType( int iPanelType ){ m_iPanelType = iPanelType; }
+
+ virtual void FireGameEvent( IGameEvent *event );
+
+private:
+ void ReadColor( const char* pszToken, Color& color );
+
+ bool m_bDrawingLines;
+ float m_fLastMapLine;
+ int m_iMouseX, m_iMouseY;
+ int m_nWhiteTexture;
+
+ Color m_colorLine;
+
+ CUtlVector<MapLine> m_vecDrawnLines[MAX_PLAYERS+1];
+
+ int m_iPanelType;
+ bool m_bTeamColors;
+};
+
+#endif // DRAWING_PANEL_H
diff --git a/game/client/tf/vgui/dynamic_recipe_subpanel.cpp b/game/client/tf/vgui/dynamic_recipe_subpanel.cpp
new file mode 100644
index 0000000..35ad19a
--- /dev/null
+++ b/game/client/tf/vgui/dynamic_recipe_subpanel.cpp
@@ -0,0 +1,2013 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "crafting_panel.h"
+#include "dynamic_recipe_subpanel.h"
+#include "vgui/ISurface.h"
+#include "vgui/ISystem.h"
+#include "c_tf_player.h"
+#include "gamestringpool.h"
+#include "iclientmode.h"
+#include "tf_item_inventory.h"
+#include "ienginevgui.h"
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/CheckButton.h"
+#include "vgui_controls/ComboBox.h"
+#include <vgui_controls/TextEntry.h>
+#include "vgui/IInput.h"
+#include "gcsdk/gcclient.h"
+#include "gcsdk/gcclientjob.h"
+#include "character_info_panel.h"
+#include "charinfo_loadout_subpanel.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "tf_hud_notification_panel.h"
+#include "tf_hud_chat.h"
+#include "c_tf_gamestats.h"
+#include "confirm_dialog.h"
+#include "econ_notifications.h"
+#include "gc_clientsystem.h"
+#include "charinfo_loadout_subpanel.h"
+#include "item_selection_criteria.h"
+#include "rtime.h"
+#include "c_tf_freeaccount.h"
+#include "econ_dynamic_recipe.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+static CDynamicRecipePanel* g_DynamicRecipePanel = NULL;
+extern const char *g_szItemBorders[AE_MAX_TYPES][5];
+
+//-----------------------------------------------------------------------------
+// Purpose: Default to NULL item
+//-----------------------------------------------------------------------------
+CRecipeComponentItemModelPanel::CRecipeComponentItemModelPanel( vgui::Panel *parent, const char *name )
+ : CItemModelPanel( parent, name )
+ , m_nPageNumber( 0 )
+{
+ SetItem( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add recipe to our list of recipes. Each call to this function
+// effectively adds an item to the next page
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::AddRecipe( itemid_t nRecipe )
+{
+ RecipeItem_t& recipeItem = m_vecRecipes[m_vecRecipes.AddToTail()];
+ recipeItem.m_nRecipeIndex = nRecipe;
+ UpdateRecipeItem( &recipeItem );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Wipe all recipes from all pages
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::DeleteRecipes()
+{
+ m_vecDefaultItems.Purge();
+ m_vecRecipes.Purge();
+ SetItem( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Override to set the item to be our default item if NULL is passed in.
+// Also handles greying out the panels
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::SetItem( const CEconItemView *pItem )
+{
+ // Use the default item if they set NULL
+ if( pItem == NULL )
+ {
+ SetBlankState();
+ }
+ else
+ {
+ BaseClass::SetItem( pItem );
+ SetGreyedOut( NULL );
+ }
+
+ InvalidateLayout( true );
+}
+
+
+void CRecipeComponentItemModelPanel::SetBlankState()
+{
+ CEconItemView* pDefaultItem = NULL;
+ if( m_nPageNumber < m_vecDefaultItems.Count() )
+ pDefaultItem = m_vecDefaultItems[ m_nPageNumber ];
+ BaseClass::SetItem( pDefaultItem );
+ SetGreyedOut( "" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move a recipe item to a specific page
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::SetRecipeItem( itemid_t nRecipeItem, int nPageNumber )
+{
+ Assert( nPageNumber < m_vecRecipes.Count() );
+ m_vecRecipes[ nPageNumber ].m_nRecipeIndex = nRecipeItem;
+
+ UpdateRecipeItem( &m_vecRecipes[ nPageNumber ] );
+
+ // Use the item that this item ID maps to
+ SetItem( m_vecRecipes[ nPageNumber ].m_pRecipeItem );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a default item to a page
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::AddDefaultItem( CEconItemView *pItem )
+{
+ m_vecDefaultItems[ m_vecDefaultItems.AddToTail() ] = pItem;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a recipe item on a given page
+//-----------------------------------------------------------------------------
+CEconItemView* CRecipeComponentItemModelPanel::GetRecipeItem( int nPageNumber ) const
+{
+ if( nPageNumber < m_vecRecipes.Count() )
+ {
+ return m_vecRecipes[nPageNumber].m_pRecipeItem;
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the recipe index from a specific page
+//-----------------------------------------------------------------------------
+itemid_t CRecipeComponentItemModelPanel::GetRecipeIndex( int nPageNumber ) const
+{
+ if( nPageNumber < m_vecRecipes.Count() )
+ {
+ return m_vecRecipes[nPageNumber].m_nRecipeIndex;
+ }
+
+ return INVALID_ITEM_ID;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Iterate through all attributes on a item and turn recipe attributes
+// into input and output items. Store those items in vectors for inputs
+// and outputs. Inputs are sorted from least common to most common
+// so that the later pages are filled with more repeats than the early pages
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::CRecipeComponentAttributeCounter::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value )
+{
+ static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" );
+ Assert( pAttrib_CannotTrade );
+
+ unsigned nCount = value.num_required() - value.num_fulfilled();
+
+ if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT )
+ {
+ CEconItem* pItem = m_vecTempEconItems[m_vecTempEconItems.AddToTail( new CEconItem() )];
+ DecodeItemFromEncodedAttributeString( value, pItem );
+
+ for( unsigned i=0; i < nCount; ++i )
+ {
+ CEconItemView& item = m_vecOutputItems[ m_vecOutputItems.AddToTail() ];
+
+ item.SetItemDefIndex( pItem->GetDefinitionIndex() );
+ item.SetItemQuality( pItem->GetQuality() );
+ item.SetItemLevel( pItem->GetItemLevel() );
+ item.SetItemID( pItem->GetItemID() );
+ item.SetNonSOEconItem( pItem ); // Set the item into the econ item view.
+ item.SetInitialized( true );
+ item.SetItemOriginOverride( kEconItemOrigin_RecipeOutput ); // Spoof where we came from
+
+ // Set the untradable flag if the attribute says so
+ if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_UNTRADABLE )
+ {
+ item.GetAttributeList()->SetRuntimeAttributeValue( pAttrib_CannotTrade, 0.0f ); // value doesn't matter -- we only check for presence/absence
+ }
+ }
+ }
+ else
+ {
+ m_nInputCount += nCount;
+
+ CEconItem* pItem = m_vecTempEconItems[m_vecTempEconItems.AddToTail( new CEconItem() )];
+ DecodeItemFromEncodedAttributeString( value, pItem );
+
+ CUtlVector<InputComponent_t>& inputSeries = m_vecInputItems[ m_vecInputItems.AddToTail() ];
+ for( unsigned i=0; i < nCount; ++i )
+ {
+ InputComponent_t& item = inputSeries[ inputSeries.AddToTail() ];
+ item.m_ItemView.SetItemDefIndex( pItem->GetDefinitionIndex() );
+ item.m_ItemView.SetItemQuality( pItem->GetQuality() );
+ item.m_ItemView.SetItemLevel( 0 );
+ item.m_ItemView.SetItemID( pItem->GetItemID() );
+ item.m_ItemView.SetNonSOEconItem( pItem ); // Set the item into the econ item view.
+ item.m_ItemView.SetInitialized( true );
+ item.m_ItemView.SetItemOriginOverride( kEconItemOrigin_RecipeOutput ); // Spoof where we came from
+ item.m_pAttrib = pAttrDef;
+ }
+
+ m_vecInputItems.Sort( LeastCommonInputSortFunc );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the output item on a given page
+//-----------------------------------------------------------------------------
+CEconItemView* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetOutputItem( int i )
+{
+ if( i >= 0 && i < m_vecOutputItems.Count() )
+ return &m_vecOutputItems[i];
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the input item on a given page
+//-----------------------------------------------------------------------------
+CEconItemView* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputItem( int i )
+{
+ InputComponent_t* pInputComponent = GetInputComponent( i );
+ if( pInputComponent )
+ {
+ return &pInputComponent->m_ItemView;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the attribute that the item in a given panel maps to
+//-----------------------------------------------------------------------------
+const CEconItemAttributeDefinition* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputAttrib( int i )
+{
+ InputComponent_t* pInputComponent = GetInputComponent( i );
+ if( pInputComponent )
+ {
+ return pInputComponent->m_pAttrib;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sort input vectors based on count. Fewer first.
+//-----------------------------------------------------------------------------
+int CDynamicRecipePanel::CRecipeComponentAttributeCounter::LeastCommonInputSortFunc( const CCopyableUtlVector<InputComponent_t> *p1, const CCopyableUtlVector<InputComponent_t> *p2 )
+{
+ return p1->Count() > p2->Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Find input component i from our 2D vector of input items
+//-----------------------------------------------------------------------------
+CDynamicRecipePanel::CRecipeComponentAttributeCounter::InputComponent_t* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputComponent( int i )
+{
+ int nAccum = 0;
+ FOR_EACH_VEC( m_vecInputItems, nIndex )
+ {
+ int nCount = m_vecInputItems[ nIndex ].Count();
+
+ if( i < nAccum + nCount )
+ {
+ return &m_vecInputItems[ nIndex ][ i - nAccum ];
+ }
+
+ nAccum += nCount;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all data in the iterator
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::CRecipeComponentAttributeCounter::Reset()
+{
+ m_vecInputItems.Purge();
+ m_vecOutputItems.Purge();
+ m_vecTempEconItems.PurgeAndDeleteElements();
+ m_nInputCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Compare Check if m_pItemToMatch passes the criteria of any of the
+// attributes on m_pSourceItem.
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::CDynamicRecipeItemMatchFind::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value )
+{
+ // Can't match ourself
+ if( m_pSourceItem && m_pItemToMatch && m_pSourceItem->GetID() == m_pItemToMatch->GetID() )
+ return true;
+
+ if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT )
+ return true;
+
+ if( !DefinedItemAttribMatch( value, m_pItemToMatch ) )
+ return true;
+
+ // Must be useable in crafting. Expensive -- do this last.
+ if( !m_pItemToMatch || !m_pItemToMatch->IsUsableInCrafting() )
+ return true;
+
+ // A match!
+ m_bMatchesAny = true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Delete all recipe data
+//-----------------------------------------------------------------------------
+void CInputPanelItemModelPanel::DeleteRecipes()
+{
+ CRecipeComponentItemModelPanel::DeleteRecipes();
+ m_vecAttrDef.Purge();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add component info to a new page
+//-----------------------------------------------------------------------------
+void CInputPanelItemModelPanel::AddComponentInfo( const CEconItemAttributeDefinition *pComponentAttrib )
+{
+ m_vecAttrDef[ m_vecAttrDef.AddToTail() ] = pComponentAttrib;
+ AddRecipe( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check if a passed in database item matches the desired item in this
+// item panel. Default to the current page
+//-----------------------------------------------------------------------------
+bool CInputPanelItemModelPanel::MatchesAttribCriteria( itemid_t itemID ) const
+{
+ return MatchesAttribCriteria( itemID, m_nPageNumber );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Check if a passed in database item matches the desired item in this
+// item panel on the specified page.
+//-----------------------------------------------------------------------------
+bool CInputPanelItemModelPanel::MatchesAttribCriteria( itemid_t itemID, int nPageNumber ) const
+{
+ if( !m_pDynamicRecipeItem )
+ return false;
+
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+ if ( !pLocalInv )
+ return false;
+
+ const CEconItemView* pItem = pLocalInv->GetInventoryItemByItemID( itemID );
+
+ if( !pItem || !pItem->IsUsableInCrafting() )
+ return false;
+
+ const CEconItemAttributeDefinition* pAttrDef = GetAttrib( nPageNumber );
+ CAttribute_DynamicRecipeComponent attribValue;
+ if( m_pDynamicRecipeItem->FindAttribute<CAttribute_DynamicRecipeComponent >( pAttrDef, &attribValue ) )
+ {
+ return DefinedItemAttribMatch( attribValue, pItem );
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the attribute that this panel represents
+//-----------------------------------------------------------------------------
+const CEconItemAttributeDefinition* CInputPanelItemModelPanel::GetAttrib( int nPageNumber ) const
+{
+ if( nPageNumber >= 0 && nPageNumber < m_vecAttrDef.Count() )
+ return m_vecAttrDef[ nPageNumber ];
+
+ return NULL;
+}
+
+
+void CInputPanelItemModelPanel::SetBlankState()
+{
+ // Get the default item
+ CEconItemView* pDefaultItem = NULL;
+ if( m_nPageNumber < m_vecDefaultItems.Count() )
+ pDefaultItem = m_vecDefaultItems[ m_nPageNumber ];
+
+ // Grey out
+ SetGreyedOut( "" );
+
+ // Check for the "item name text override" attribute on the default item
+ static CSchemaAttributeDefHandle pAttrDef_ItemNameTextOverride( "item name text override" );
+ CAttribute_String attrItemNameTextOverride;
+ if ( pDefaultItem )
+ {
+ pDefaultItem->FindAttribute( pAttrDef_ItemNameTextOverride, &attrItemNameTextOverride );
+
+ if ( FStrEq( attrItemNameTextOverride.value().c_str(), "#TF_ItemName_Item" ) )
+ {
+ // This is a dummy item. Dont display an icon. Just the name of the item
+ CItemModelPanel::SetItem( NULL );
+ SetAttribOnly( true );
+ SetTextYPos( 0 );
+ //SetNoItemText( pDefaultItem->GetItemName(), NULL, NULL );
+ const wchar_t *pszItemname = pDefaultItem->GetItemName();
+ if ( V_wcscmp( pszItemname, g_pVGuiLocalize->Find( "#TF_ItemName_Item" ) ) == 0 && pDefaultItem->GetQuality() == AE_PAINTKITWEAPON )
+ {
+ SetNoItemText( g_pVGuiLocalize->Find( "paintkitweapon" ), NULL, NULL );
+ }
+ else
+ {
+ SetNoItemText( pDefaultItem->GetItemName(), NULL, NULL );
+ }
+ }
+ else
+ {
+ BaseClass::SetItem( pDefaultItem );
+ }
+ }
+ else
+ {
+ // Construct an item from the attribute that describes this input
+ CEconItem tempItem;
+ const CEconItemAttributeDefinition* pAttrDef = GetAttrib( m_nPageNumber );
+ CAttribute_DynamicRecipeComponent attribValue;
+ if( m_pDynamicRecipeItem->FindAttribute<CAttribute_DynamicRecipeComponent >( pAttrDef, &attribValue ) )
+ {
+ DecodeItemFromEncodedAttributeString( attribValue, &tempItem );
+ }
+
+ // Shove it into an econitemview
+ CEconItemView tempView;
+ tempView.Init( tempItem.GetItemDefIndex(), tempItem.GetQuality(), 0 );
+ tempView.SetNonSOEconItem( &tempItem );
+
+ // Set its name as the text for this item model panel
+ CItemModelPanel::SetItem( NULL );
+ SetAttribOnly( true );
+ SetTextYPos( 0 );
+ SetNoItemText( tempView.GetItemName(), NULL, NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is this in play
+//-----------------------------------------------------------------------------
+bool CRecipeComponentItemModelPanel::IsSlotAvailable( int nPageNumber )
+{
+ return nPageNumber < m_vecRecipes.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the passed in recipe item and changes the item we show
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::UpdateRecipeItem( RecipeItem_t* pRecipeItem )
+{
+ Assert( pRecipeItem );
+
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+ if ( pLocalInv == NULL )
+ return;
+
+ pRecipeItem->m_pRecipeItem = pLocalInv->GetInventoryItemByItemID( pRecipeItem->m_nRecipeIndex );
+ SetItem( pRecipeItem->m_pRecipeItem );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the item we show for the current page
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::UpdateDisplayItem()
+{
+ if( m_nPageNumber < m_vecRecipes.Count() )
+ {
+ UpdateRecipeItem( &m_vecRecipes[ m_nPageNumber ] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the current page
+//-----------------------------------------------------------------------------
+void CRecipeComponentItemModelPanel::SetPageNumber( int nPageNumber )
+{
+ Assert( nPageNumber >= 0 );
+ m_nPageNumber = nPageNumber;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CDynamicRecipePanel::CDynamicRecipePanel( vgui::Panel *parent, const char *panelName, CEconItemView* pRecipeItem )
+ : CBackpackPanel( parent, panelName )
+ , m_pDynamicRecipeItem( pRecipeItem )
+ , m_pRecipeCraftButton( NULL )
+ , m_nNumRecipeItems( 0 )
+ , m_bAllRecipePanelsFilled( false )
+ , m_bInputPanelsDirty( false )
+ , m_nInputPage( 0 )
+ , m_nOutputPage( 0 )
+ , m_pCurInputPageLabel( NULL )
+ , m_pNextInputPageButton( NULL )
+ , m_pPrevInputPageButton( NULL )
+ , m_flAbortCraftingAt( 0 )
+ , m_pMouseOverItemPanel( NULL )
+ , m_pNoMatchesLabel( NULL )
+ , m_pUntradableOutputsLabel( NULL )
+ , m_bShowUntradable( false )
+
+{
+ g_DynamicRecipePanel = this;
+
+ m_pRecipeContainer = new vgui::EditablePanel( this, "recipecontainer" );
+ m_pInventoryContainer = new vgui::EditablePanel( this, "inventorycontainer" );
+ m_pShowUntradableItemsCheckbox = new vgui::CheckButton( m_pInventoryContainer, "untradablecheckbox", "#Dynamic_Recipe_Untradable_Checkbox" );
+ m_pShowUntradableItemsCheckbox->AddActionSignalTarget( this );
+ m_pInputsLabel = new CExLabel( m_pRecipeContainer, "InputLabel", "#Craft_Recipe_Inputs" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CDynamicRecipePanel::~CDynamicRecipePanel( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get all our controls
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( GetResFile() );
+
+ // This calls AddNewItemPanel
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ CExButton* pCancelButton = dynamic_cast<CExButton*>( m_pInventoryContainer->FindChildByName("CancelButton") );
+ if ( pCancelButton )
+ pCancelButton->AddActionSignalTarget( this );
+
+ m_pRecipeCraftButton = dynamic_cast<CExButton*>( m_pRecipeContainer->FindChildByName("CraftButton") );
+ if ( m_pRecipeCraftButton )
+ m_pRecipeCraftButton->AddActionSignalTarget( this );
+
+ m_pNextPageButton = dynamic_cast<CExButton*>( m_pInventoryContainer->FindChildByName("NextPageButton") );
+ if( m_pNextPageButton )
+ m_pNextPageButton->AddActionSignalTarget( this );
+
+ m_pPrevPageButton = dynamic_cast<CExButton*>( m_pInventoryContainer->FindChildByName("PrevPageButton") );
+ if( m_pPrevPageButton )
+ m_pPrevPageButton->AddActionSignalTarget( this );
+
+ m_pCurPageLabel = dynamic_cast<vgui::Label*>( m_pInventoryContainer->FindChildByName("CurPageLabel") );
+ Assert( m_pCurPageLabel );
+
+ m_pNoMatchesLabel = dynamic_cast<CExLabel*>( m_pInventoryContainer->FindChildByName( "NoMatches" ) );
+ Assert( m_pNoMatchesLabel );
+
+ m_pUntradableOutputsLabel = dynamic_cast<CExLabel*>( m_pRecipeContainer->FindChildByName( "UntradableLabel" ) );
+ Assert( m_pUntradableOutputsLabel );
+
+ m_pOutputsLabel = dynamic_cast<CExLabel*>( m_pRecipeContainer->FindChildByName( "OutputLabel" ) );
+ Assert( m_pOutputsLabel );
+
+ m_pCurInputPageLabel = dynamic_cast<CExLabel*>( m_pRecipeContainer->FindChildByName( "CurInputPageLabel" ) );
+
+ m_pNextInputPageButton = dynamic_cast<CExButton*>( m_pRecipeContainer->FindChildByName( "NextInputPageButton" ) );
+ if( m_pNextInputPageButton )
+ m_pNextInputPageButton->AddActionSignalTarget( this );
+
+ m_pPrevInputPageButton = dynamic_cast<CExButton*>( m_pRecipeContainer->FindChildByName( "PrevInputPageButton" ) );
+ if( m_pPrevInputPageButton )
+ m_pPrevInputPageButton->AddActionSignalTarget( this );
+
+#ifdef STAGING_ONLY
+ m_pDevGiveInputsButton = new CExButton( m_pInventoryContainer, "dev_giveinputsbutton", "[Debug] Give Inputs", this, "dev_giveinputs" );
+ m_pDevGiveInputsButton->SetEnabled( true );
+ m_pDevGiveInputsButton->SetVisible( true );
+#endif
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update all the item panels and page buttons and labels
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+#ifdef STAGING_ONLY
+ if( m_pDevGiveInputsButton )
+ {
+ m_pDevGiveInputsButton->SetPos( XRES(0) , YRES(290) );
+ m_pDevGiveInputsButton->SetWide( 210 );
+ m_pDevGiveInputsButton->SetTall( 40 );
+ m_pDevGiveInputsButton->MoveToFront();
+ m_pDevGiveInputsButton->SetEnabled( true );
+ m_pDevGiveInputsButton->SetVisible( true );
+ }
+#endif
+
+ if( m_pSortByComboBox )
+ {
+ m_pSortByComboBox->SetVisible( false );
+ }
+
+ UpdateModelPanels();
+
+ bool bNoMatches = m_nNumRecipeItems == 0;
+ bool bMultiplePagesOfMatches = m_nNumRecipeItems > (unsigned)GetNumBackpackPanelsPerPage();
+ // Some panels show and hide based on the number of matches
+ if( m_pNoMatchesLabel)
+ m_pNoMatchesLabel->SetVisible( bNoMatches );
+ if( m_pNextPageButton )
+ m_pNextPageButton->SetVisible( bMultiplePagesOfMatches );
+ if( m_pPrevPageButton )
+ m_pPrevPageButton->SetVisible( bMultiplePagesOfMatches );
+ if( m_pCurPageLabel )
+ m_pCurPageLabel->SetVisible( bMultiplePagesOfMatches );
+
+ bool bMultiplePagesOfInputs = m_RecipeIterator.GetInputCount() > GetNumInputPanelsPerPage();
+ if( m_pNextInputPageButton )
+ m_pNextInputPageButton->SetVisible( bMultiplePagesOfInputs );
+ if( m_pNextInputPageButton )
+ m_pNextInputPageButton->SetEnabled( m_nInputPage < GetNumInputPages() - 1 );
+ if( m_pCurInputPageLabel )
+ m_pCurInputPageLabel->SetVisible( bMultiplePagesOfInputs );
+ if( m_pPrevInputPageButton )
+ m_pPrevInputPageButton->SetVisible( bMultiplePagesOfInputs );
+ if( m_pPrevInputPageButton )
+ m_pPrevInputPageButton->SetEnabled( m_nInputPage > 0 );
+
+ if( m_pUntradableOutputsLabel )
+ m_pUntradableOutputsLabel->SetVisible( m_pDynamicRecipeItem ? !m_pDynamicRecipeItem->IsTradable() : false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle commands
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "back", 4 ) )
+ {
+ PostMessage( GetParent(), new KeyValues("CraftingClosed") );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "craft", 5 ) )
+ {
+ // Check if we should warn about partial completion
+ if( WarnAboutPartialCompletion() )
+ {
+ if( CheckForUntradableItems() )
+ {
+ Craft();
+ }
+ }
+
+ return;
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( true, true ); // deliberatly fallthrough to baseclass
+ }
+ else if( !Q_stricmp( command, "cancel" ) )
+ {
+ SetVisible( false );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "nextpage", 8 ) )
+ {
+ InvalidateLayout(); // deliberatly fallthrough to baseclass
+ }
+ else if ( !Q_strnicmp( command, "prevpage", 8 ) )
+ {
+ InvalidateLayout(); // deliberatly fallthrough to baseclass
+ }
+ else if( !Q_strnicmp( command, "nextinputpage", 13 ) )
+ {
+ if( m_nInputPage < GetNumInputPages() )
+ m_nInputPage++;
+ InvalidateLayout();
+ return;
+ }
+ else if( !Q_strnicmp( command, "previnputpage", 13 ) )
+ {
+ if( m_nInputPage > 0 )
+ m_nInputPage--;
+ InvalidateLayout();
+ return;
+ }
+ else if( !Q_strnicmp( command, "deleteitem", 10 ) ||
+ !Q_strnicmp( command, "useitem", 7 ) )
+ {
+ // Gobble up these commands
+ return;
+ }
+#ifdef STAGING_ONLY
+ else if( !Q_strnicmp( command, "dev_giveinputs", 14 ) )
+ {
+ Debug_GiveRequiredInputs();
+ if( m_pDevGiveInputsButton )
+ m_pDevGiveInputsButton->SetEnabled( false );
+ }
+#endif
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gobble up all keyboard input for now!
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnKeyCodePressed( vgui::KeyCode /*code*/ )
+{
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnButtonChecked( KeyValues *pData )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( pData->GetPtr("panel") );
+
+ if ( m_pShowUntradableItemsCheckbox == pPanel )
+ {
+ if ( m_bShowUntradable != m_pShowUntradableItemsCheckbox->IsSelected() )
+ {
+ m_bShowUntradable = m_pShowUntradableItemsCheckbox->IsSelected();
+ InitItemPanels();
+ UpdateModelPanels();
+ }
+ }
+}
+
+
+int CDynamicRecipePanel::GetNumItemPanels( void )
+{
+ return DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT + DYNAMIC_RECIPE_PACKPACK_COUNT_PER_PAGE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see that each and every input panel has a recipe item in it
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::AllRecipePanelsFilled( void )
+{
+ // Need to recalculate
+ if( m_bInputPanelsDirty )
+ {
+ // Assume all filled, and go through and try to find one that's empty
+ m_bAllRecipePanelsFilled = true;
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ CInputPanelItemModelPanel* pInputPanel = m_vecRecipeInputModelPanels[i];
+ for( int j=0; j < GetNumInputPages(); ++j )
+ {
+ if( pInputPanel->GetRecipeItem( j ) == NULL && pInputPanel->GetAttrib( j ) != NULL )
+ {
+ m_bAllRecipePanelsFilled = false;
+ break;
+ }
+ }
+ }
+ }
+
+ return m_bAllRecipePanelsFilled;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Callback for the confirm partial completion dialog
+//-----------------------------------------------------------------------------
+void ConfirmDestroyItems( bool bConfirmed, void* pContext )
+{
+ CDynamicRecipePanel *pRecipePanel = ( CDynamicRecipePanel* )pContext;
+ if ( pRecipePanel && bConfirmed )
+ {
+ if( pRecipePanel->CheckForUntradableItems() )
+ {
+ pRecipePanel->Craft();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Callback for the conrim untradable dialog
+//-----------------------------------------------------------------------------
+static void ConfirmUntradableCraft( bool bConfirmed, void* pContext )
+{
+ CDynamicRecipePanel *pRecipePanel = ( CDynamicRecipePanel* )pContext;
+ if ( pRecipePanel && bConfirmed )
+ {
+ pRecipePanel->Craft();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Go through each input panel and find out if any of them contain an
+// item that is untradeable
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::CheckForUntradableItems( void )
+{
+ // We dont care if the recipe itself is already not tradable
+ if( !m_pDynamicRecipeItem->IsTradable() )
+ {
+ return true;
+ }
+
+ bool bHasUntradable = false;
+ for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; ++i )
+ {
+ CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i];
+ for( int j = 0; j < GetNumInputPages(); ++j )
+ {
+ itemid_t nRecipeItemID = pPanel->GetRecipeIndex( j );
+
+ if ( nRecipeItemID != 0 && nRecipeItemID != INVALID_ITEM_ID )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( nRecipeItemID );
+ if ( pItemData->IsTradable() == false )
+ {
+ bHasUntradable = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( bHasUntradable )
+ {
+ enum { kWarningLength = 512 };
+ locchar_t wszWarning[ kWarningLength ] = LOCCHAR("");
+
+ loc_scpy_safe( wszWarning,
+ CConstructLocalizedString( GLocalizationProvider()->Find( "Dynamic_Recipe_Untradable_Text" ),
+ m_pDynamicRecipeItem->GetItemName() ) );
+
+ CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#Craft_Untradable_Title", wszWarning, "#GameUI_OK", "#Cancel", &ConfirmUntradableCraft, NULL );
+
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ pDialog->Show();
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Check if not all of the inputs have items set into them. Put up
+// a prompt and return false if so.
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::WarnAboutPartialCompletion( void )
+{
+ if( !AllRecipePanelsFilled() )
+ {
+ enum { kWarningLength = 512 };
+ locchar_t wszWarning[ kWarningLength ] = LOCCHAR("");
+
+ loc_scpy_safe( wszWarning,
+ CConstructLocalizedString( GLocalizationProvider()->Find( "Dynamic_Recipe_Partial_Completion_Warning" ),
+ m_pDynamicRecipeItem->GetItemName() ) );
+
+ CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#Craft_Untradable_Title", wszWarning, "#GameUI_OK", "#Cancel", &ConfirmDestroyItems, NULL );
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ pDialog->Show();
+ }
+
+
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: They hit the craft button! Cook up a message that we're going to send
+// to the GC that contains all of the item_ids and attributes they are
+// to apply to. Open a crafting status dialog when after we send the message.
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::Craft()
+{
+ GCSDK::CProtoBufMsg<CMsgFulfillDynamicRecipeComponent> msg( k_EMsgGCFulfillDynamicRecipeComponent );
+ msg.Body().set_tool_item_id( m_pDynamicRecipeItem->GetItemID() );
+
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i];
+
+ for( int j=0; j < GetNumInputPages(); ++j )
+ {
+ itemid_t nRecipeItemID = pPanel->GetRecipeIndex( j );
+
+ if( nRecipeItemID != 0 && nRecipeItemID != INVALID_ITEM_ID )
+ {
+ if( !pPanel->MatchesAttribCriteria( nRecipeItemID, j ) )
+ {
+ AssertMsg( 0, "Input panel has recipe item that does not pass its criteria" );
+ // Something bad happened. Return this item to the backpack.
+ ReturnRecipeItemToBackpack( nRecipeItemID, pPanel, j );
+ continue;
+ }
+
+ // Add component
+ CMsgRecipeComponent* pComponent = msg.Body().add_consumption_components();
+ pComponent->set_subject_item_id( nRecipeItemID );
+
+ const CEconItemAttributeDefinition* pAttribute = pPanel->GetAttrib( j );
+ if( !pAttribute )
+ {
+ AssertMsg( 0, "NULL attribute in panel what attempting to craft" );
+ // Something bad happened. Return this item to the backpack.
+ ReturnRecipeItemToBackpack( nRecipeItemID, pPanel, j );
+ continue;
+ }
+ pComponent->set_attribute_index( pAttribute->GetDefinitionIndex() );
+ }
+ }
+ }
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pDynamicRecipeItem, "consumed_item" );
+ GCClientSystem()->BSendMessage( msg );
+
+ // Open a craft status window to take focus away and let the user know something is happening
+ OpenCraftingStatusDialog( this, "#CraftUpdate_Start", true, false, false );
+ m_flAbortCraftingAt = vgui::system()->GetCurrentTime() + 10;
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Think when we're waiting for a craft response
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if( IsVisible() )
+ {
+ if( m_flAbortCraftingAt != 0.f )
+ {
+ // Timeout for crafting. Let them know we failed.
+ if( m_flAbortCraftingAt < vgui::system()->GetCurrentTime() )
+ {
+ OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false );
+ m_flAbortCraftingAt = 0;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Close the craft status window if we close
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
+{
+ if ( !bVisible )
+ {
+ CloseCraftingStatusDialog();
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+ BaseClass::OnShowPanel( bVisible, bReturningFromArmory );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add the new panels into vectors for each group, and set parents to
+// appropriate frames
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::AddNewItemPanel( int iPanelIndex )
+{
+ if( IsInputPanel( iPanelIndex ) )
+ {
+ // Input item
+ int nIndex = m_vecRecipeInputModelPanels.AddToTail();
+ CInputPanelItemModelPanel* pPanel = vgui::SETUP_PANEL( new CInputPanelItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex), m_pDynamicRecipeItem ));
+ m_pItemModelPanels.AddToTail( pPanel );
+ pPanel->SetParent( m_pRecipeContainer );
+ m_vecRecipeInputModelPanels[nIndex] = pPanel;
+ }
+ else if( IsOutputPanel( iPanelIndex ) )
+ {
+ // Output item
+ CItemModelPanel* pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex) ) );
+ m_vecRecipeOutputModelPanels.AddToTail( pPanel );
+ m_pItemModelPanels.AddToTail( pPanel );
+ pPanel->SetParent( m_pRecipeContainer );
+ }
+ else
+ {
+ // Inventory item
+ int nIndex = m_vecBackpackModelPanels.AddToTail();
+ CRecipeComponentItemModelPanel* pPanel = vgui::SETUP_PANEL( new CRecipeComponentItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex) ) );
+ m_vecBackpackModelPanels[nIndex] = pPanel;
+ m_pItemModelPanels.AddToTail( pPanel );
+ pPanel->SetParent( m_pInventoryContainer );
+ }
+
+ CItemModelPanel* pPanel = m_pItemModelPanels.Tail();
+ pPanel->SetActAsButton( true, true );
+ pPanel->SetTooltip( m_pMouseOverTooltip, "" );
+ pPanel->SetShowGreyedOutTooltip( true );
+ pPanel->SetShowEquipped( true );
+
+ // Store a position for our new panel
+ m_ItemModelPanelPos.AddToTail();
+ m_ItemModelPanelPos[iPanelIndex].x = m_ItemModelPanelPos[iPanelIndex].y = 0;
+
+ Assert( iPanelIndex == (m_pItemModelPanels.Count()-1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is this index an input panel
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::IsInputPanel( int iPanelIndex ) const
+{
+ return iPanelIndex < DYNAMIC_RECIPE_INPUT_COUNT;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is this index an output panel
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::IsOutputPanel( int iPanelIndex) const
+{
+ return iPanelIndex < ( DYNAMIC_RECIPE_OUTPUT_COUNT + DYNAMIC_RECIPE_INPUT_COUNT ) && !IsInputPanel(iPanelIndex);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is this index a backpack panel
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::IsBackpackPanel( int iPanelIndex ) const
+{
+ return ( iPanelIndex >= ( DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT )
+ && !IsInputPanel( iPanelIndex )
+ && !IsOutputPanel( iPanelIndex ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is this backpack panel on this page
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::IsInvPanelOnThisPage( unsigned nIndex ) const
+{
+ unsigned nNumPerPage = DYNAMIC_RECIPE_BACKPACK_ROWS * DYNAMIC_RECIPE_BACKPACK_COLS;
+ unsigned nMinIndex = nNumPerPage * GetCurrentPage();
+ unsigned nMaxIndex = nNumPerPage * (GetCurrentPage() + 1);
+
+ return nIndex >= nMinIndex && nIndex < nMaxIndex;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given the number of items that match the recipe, how many pages
+// do we need to show
+//-----------------------------------------------------------------------------
+int CDynamicRecipePanel::GetNumPages()
+{
+ return ceil( float(m_nNumRecipeItems) / float(GetNumBackpackPanelsPerPage()) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the page of the backpack panels
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::SetCurrentPage( int nNewPage )
+{
+ if ( nNewPage < 0 )
+ {
+ nNewPage = GetNumPages() - 1;
+ }
+ else if ( nNewPage >= GetNumPages() )
+ {
+ nNewPage = 0;
+ }
+
+ FOR_EACH_VEC( m_vecBackpackModelPanels, i )
+ {
+ m_vecBackpackModelPanels[i]->SetPageNumber( nNewPage );
+ }
+
+ BaseClass::SetCurrentPage( nNewPage );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Chance the current page for all input panels
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::SetCurrentInputPage( int nNewPage )
+{
+ m_nInputPage = nNewPage;
+
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ m_vecRecipeInputModelPanels[i]->SetPageNumber( nNewPage );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given the number of input items, how many input pages do we need to show
+//-----------------------------------------------------------------------------
+int CDynamicRecipePanel::GetNumInputPages() const
+{
+ return ceil( float(m_RecipeIterator.GetInputCount()) / float(GetNumInputPanelsPerPage()) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given the number of output items, how many output pages do we need to show
+//-----------------------------------------------------------------------------
+int CDynamicRecipePanel::GetNumOutputPage() const
+{
+ return ceil( float(m_RecipeIterator.GetOutputCount()) / float(GetNumOutputPanelsPerPage()) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset all of the item panels, their pages, their info and re-evaluate
+// the recipe item for attributes and the backpack for matching items
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::InitItemPanels()
+{
+ // Clear out every panel's items
+ FOR_EACH_VEC( m_pItemModelPanels, i )
+ {
+ m_pItemModelPanels[i]->SetItem( NULL );
+ }
+
+ // Go through and set the item to all inventory panels to NULL
+ FOR_EACH_VEC( m_vecBackpackModelPanels, i )
+ {
+ m_vecBackpackModelPanels[i]->DeleteRecipes();
+ }
+
+ // Go through our backpack and repopulate our backpack and matching components
+ FindPossibleBackpackItems();
+
+ // Reset to the first page
+ SetCurrentInputPage( 0 );
+ SetCurrentPage( 0 );
+
+ // Go through and set default items on input panels
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i];
+
+ // Clear out any recipes we have
+ pPanel->DeleteRecipes();
+ // Clear out stale info
+ pPanel->SetDynamicRecipeItem( m_pDynamicRecipeItem );
+ }
+
+ for( int i=0; i < m_RecipeIterator.GetInputCount(); ++i )
+ {
+ CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[ i % m_vecRecipeInputModelPanels.Count() ];
+
+ pPanel->AddDefaultItem( m_RecipeIterator.GetInputItem( i ) );
+ pPanel->AddComponentInfo( m_RecipeIterator.GetInputAttrib( i ) );
+ }
+
+ // Go through and set items into output panels
+ FOR_EACH_VEC( m_vecRecipeOutputModelPanels, i )
+ {
+ CItemModelPanel* pPanel = m_vecRecipeOutputModelPanels[i];
+
+ // Set appropriate item for output panel
+ if( i < m_RecipeIterator.GetOutputCount() )
+ {
+ int nOutputIndex = (m_nOutputPage * DYNAMIC_RECIPE_OUTPUT_COUNT) + i;
+ pPanel->SetItem( m_RecipeIterator.GetOutputItem( nOutputIndex ) );
+ }
+ }
+
+ // Go through all the recipe items and set them into inventory panels
+ PopulatePanelsForCurrentPage();
+
+ UpdateModelPanels();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Fills in the backpack slots with the items for the current page
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::PopulatePanelsForCurrentPage()
+{
+ // Go through all the recipe items and set them into inventory panels
+ FOR_EACH_VEC( m_vecBackpackModelPanels, i )
+ {
+ CRecipeComponentItemModelPanel* pPanel = m_vecBackpackModelPanels[i];
+ // Update the item we display. Our inventory might have shifted around
+ pPanel->UpdateDisplayItem();
+
+ bool bIsSlotOpen = pPanel->IsSlotAvailable( GetCurrentPage() );
+ pPanel->SetVisible( bIsSlotOpen );
+ pPanel->SetEnabled( bIsSlotOpen );
+ pPanel->InvalidateLayout();
+ SetBorderForItem( pPanel, false );
+ }
+
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i];
+ pPanel->SetPageNumber( m_nInputPage );
+ pPanel->UpdateDisplayItem();
+
+ bool bIsSlotOpen = pPanel->IsSlotAvailable( m_nInputPage );
+ pPanel->SetVisible( bIsSlotOpen );
+ pPanel->SetEnabled( bIsSlotOpen );
+ pPanel->InvalidateLayout();
+ SetBorderForItem( pPanel, false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Go through the player's entire backpack and check if each of them
+// passes any of the input panel's attributes criteria
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::FindPossibleBackpackItems()
+{
+ Assert( m_pDynamicRecipeItem->GetSOCData() );
+ // Iterate through the attributes on our recipe item
+ // and create all of our input and output items.
+ m_RecipeIterator.Reset();
+ CEconItem* pEconItem = m_pDynamicRecipeItem->GetSOCData() ;
+ pEconItem->IterateAttributes( &m_RecipeIterator );
+
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+ if ( pLocalInv == NULL )
+ return;
+
+ m_nNumRecipeItems = 0;
+
+ // Go through our backpack and filter items that match our input criteria
+ for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
+ {
+ CEconItemView *pItem = pLocalInv->GetItem( i );
+ Assert( pItem );
+
+ // If we're not showing untradable items, and this item is untradeable
+ // then we just skip it
+ if ( !pItem->IsTradable() && !m_bShowUntradable )
+ continue;
+
+ CDynamicRecipeItemMatchFind matchingIterator( m_pDynamicRecipeItem, pItem );
+ m_pDynamicRecipeItem->IterateAttributes( &matchingIterator );
+ if( matchingIterator.MatchesAnyAttributes() )
+ {
+ // Set in the recipe
+ m_vecBackpackModelPanels[ m_nNumRecipeItems % GetNumBackpackPanelsPerPage() ]->AddRecipe( pItem->GetItemID() );
+ ++m_nNumRecipeItems;
+ }
+ }
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Layout the item panels
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ int iCenter = 0;
+ int iButtonX = 0, iButtonY = 0, iXPos = 0, iYPos = 0;
+
+ // Position all of the panels
+ if( IsInputPanel( iIndex ) )
+ {
+ iButtonX = (iIndex % CRAFTING_SLOTS_INPUT_COLUMNS);
+ iButtonY = (iIndex / CRAFTING_SLOTS_INPUT_COLUMNS);
+ iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ iYPos = m_iItemYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+
+ pPanel->SetPos( iXPos, iYPos );
+ }
+ else if( IsOutputPanel( iIndex ) )
+ {
+ int iButtonIndex = iIndex - DYNAMIC_RECIPE_INPUT_COUNT;
+ iButtonX = (iButtonIndex % CRAFTING_SLOTS_OUTPUT_COLUMNS);
+ iButtonY = (iButtonIndex / CRAFTING_SLOTS_OUTPUT_COLUMNS);
+ iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ iYPos = m_iOutputItemYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+
+ pPanel->SetPos( iXPos, iYPos );
+ pPanel->SetItem( m_RecipeIterator.GetOutputItem( iButtonIndex ) );
+ }
+ else if( IsBackpackPanel( iIndex ) )
+ {
+ int iButtonIndex = iIndex - DYNAMIC_RECIPE_INPUT_COUNT - DYNAMIC_RECIPE_OUTPUT_COUNT;
+ iButtonX = (iButtonIndex % DYNAMIC_RECIPE_BACKPACK_COLS);
+ iButtonY = (iButtonIndex / DYNAMIC_RECIPE_BACKPACK_COLS);
+ iXPos = (iCenter + m_iInventoryXPos) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ iYPos = m_iInventoryYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+
+ pPanel->SetPos( iXPos, iYPos );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Update each panel to see if it should show, what item to show, and if
+// it's greyed out. Update the page labels here too.
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::UpdateModelPanels( void )
+{
+ // Check if any input panels have recipe items in them
+ bool bAnyInputHasRecipe = false;
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i];
+
+ // Update the item we display. Our inventory might have shifted around
+ pPanel->UpdateDisplayItem();
+
+ SetBorderForItem( pPanel, false );
+ // Check for a recipe
+ for( int j=0; j < GetNumInputPages(); ++j )
+ {
+ CEconItemView* pRecipe = pPanel->GetRecipeItem( j );
+ bAnyInputHasRecipe |= pRecipe != NULL;
+ }
+ // Input panels are visible and enabled if their recipe slot is available
+ pPanel->SetEnabled( pPanel->GetDefaultItem() != NULL );
+ pPanel->SetVisible( pPanel->GetDefaultItem() != NULL );
+ }
+
+ static CSchemaAttributeDefHandle pAttrib_NoPartialComplete( "recipe no partial complete" );
+ bool bPartialCompletionAllowed = !m_pDynamicRecipeItem->FindAttribute( pAttrib_NoPartialComplete );
+
+ m_pInputsLabel->SetText( bPartialCompletionAllowed ? "#Craft_Recipe_Inputs" : "#Dynamic_Recipe_Outputs_No_Partial_Complete" );
+
+ // If any of the input panels have a recipe in them, then crafting is available
+ if ( m_pRecipeCraftButton )
+ {
+ bool bCraftEnabled = ( bPartialCompletionAllowed && bAnyInputHasRecipe ) || AllRecipePanelsFilled();
+ m_pRecipeCraftButton->SetEnabled( bCraftEnabled );
+ }
+ if ( m_pRecipeCraftButton )
+ {
+ m_pRecipeCraftButton->SetText( AllRecipePanelsFilled() ? "#CraftConfirm" : "#ToolCustomizeTextureOKButton" );
+ }
+ if ( m_pOutputsLabel )
+ {
+ m_pOutputsLabel->SetText( AllRecipePanelsFilled() ? "#Craft_Recipe_Outputs" : "#Dynamic_Recipe_Outputs_Not_Complete");
+ m_pOutputsLabel->SetFgColor( AllRecipePanelsFilled() ? Color( 200, 80, 60, 255 ) : Color( 117, 107, 94, 255 ) );
+ }
+
+ FOR_EACH_VEC( m_vecRecipeOutputModelPanels, i )
+ {
+ CItemModelPanel* pPanel = m_vecRecipeOutputModelPanels[i];
+ SetBorderForItem( pPanel, false );
+
+ // Output panels are visible and enabled if they have an item in them
+ pPanel->SetVisible( pPanel->GetItem() != NULL );
+ pPanel->SetEnabled( pPanel->GetItem() != NULL );
+ }
+
+ PopulatePanelsForCurrentPage();
+
+ // Update the current backpack page numbers
+ char szTmp[16];
+ Q_snprintf(szTmp, 16, "%d/%d", GetCurrentPage() + 1, GetNumPages() );
+ m_pInventoryContainer->SetDialogVariable( "backpackpage", szTmp );
+
+ // Update the current input page numbers
+ Q_snprintf(szTmp, 16, "%d/%d", m_nInputPage + 1, GetNumInputPages() );
+ m_pRecipeContainer->SetDialogVariable( "inputpage", szTmp );
+
+ DeSelectAllBackpackItemPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If this is a backpack panel, send the item into the first accepting
+// input pane, if one exists, or else do nothing. If this is a input
+// panel with a backpack item in it, send the backpack item back to the
+// backpack.
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnItemPanelMouseDoublePressed( vgui::Panel *panel )
+{
+ CRecipeComponentItemModelPanel *pSrcPanel = dynamic_cast < CRecipeComponentItemModelPanel * > ( panel );
+ if( !pSrcPanel )
+ return;
+
+ int iIndex = GetBackpackPositionForPanel( pSrcPanel );
+
+ // Send from an input panel to the first open backpack panel, even on a different page
+ if( IsInputPanel( iIndex ) )
+ {
+ CInputPanelItemModelPanel *pInputPanel = assert_cast<CInputPanelItemModelPanel*>( pSrcPanel );
+ Assert( pInputPanel );
+
+ int nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pInputPanel->GetPageNumber() );
+ CEconItemView* pRecipeItem = pInputPanel->GetRecipeItem( pInputPanel->GetPageNumber() );
+ if( pRecipeItem )
+ {
+ // Just put it in the first open slot.
+ ReturnRecipeItemToBackpack( nSrcRecipeIndex, pInputPanel, pInputPanel->GetPageNumber() );
+ }
+ }
+ else if( IsBackpackPanel( iIndex ) )
+ {
+ // Sending from the backpack panel to the first matching input panel that's on the current input page.
+ int nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( GetCurrentPage() );
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ CInputPanelItemModelPanel *pDstPanel = m_vecRecipeInputModelPanels[i];
+ if( pDstPanel->GetRecipeItem( pDstPanel->GetPageNumber() ) == NULL && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) )
+ {
+ // Next check if we just want to move the item
+ SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, pDstPanel->GetPageNumber() );
+ break;
+ }
+ }
+ }
+
+ m_bInputPanelsDirty = true;
+ // Force panels to update
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Border highlight if the panel has an item in it
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ m_pMouseOverItemPanel = pItemPanel;
+
+ CEconItemView *pItem = pItemPanel->GetItem();
+
+ // Recalc the borders on item panels
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ SetBorderForItem( m_vecRecipeInputModelPanels[i], false );
+ }
+
+ if ( pItemPanel && IsVisible() )
+ {
+ SetBorderForItem( pItemPanel, pItem != NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turn of border highlights
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnItemPanelExited( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ m_pMouseOverItemPanel = NULL;
+
+ // Recalc the borders on item panels
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ SetBorderForItem( m_vecRecipeInputModelPanels[i], false );
+ }
+
+ if ( pItemPanel && !pItemPanel->IsSelected() )
+ {
+ SetBorderForItem( pItemPanel, false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: We got a craft response! Close out this window because we're going
+// to show the user their loot
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::OnRecipeCompleted()
+{
+ SetVisible( false );
+ m_flAbortCraftingAt = 0.f;
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the border for the item. Input panels highlight when a dragged
+// item matches its criteria. Output items highlight when all inputs
+// are fulfilled.
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
+{
+ if ( !pItemPanel || !pItemPanel->IsVisible() )
+ return;
+
+ int iIndex = GetBackpackPositionForPanel( pItemPanel );
+ if( IsInputPanel( iIndex ) )
+ {
+ // Special case for input panels. They need to highlight when a dragged item
+ // matches their criteria.
+ CItemModelPanel* pPanel = m_bDragging ? m_pMouseDragItemPanel : m_pMouseOverItemPanel;
+ CEconItemView* pPanelItem = pPanel ? pPanel->GetItem() : NULL;
+
+ CInputPanelItemModelPanel* pInputPanel = dynamic_cast<CInputPanelItemModelPanel*>( pItemPanel );
+ Assert( pInputPanel );
+ // If this panel doesnt have a recipe, then we want some sort of greyed out look
+ if( pInputPanel->GetRecipeItem( pInputPanel->GetPageNumber() ) == NULL )
+ {
+ const char *pszBorder = NULL;
+ int iRarity = GetItemQualityForBorder( pItemPanel );
+
+ // We only want backpack panels and the drag panel to be highlight sources
+ bool bValidHighlightSourcePanel = pPanel == m_pMouseDragItemPanel
+ || m_vecBackpackModelPanels.Find( static_cast<CRecipeComponentItemModelPanel*>(pPanel) ) != m_vecBackpackModelPanels.InvalidIndex();
+
+ // If this panel can accept whatever the source panel is, we want to highlight a bit
+ if( bValidHighlightSourcePanel && pPanelItem && pInputPanel->MatchesAttribCriteria( pPanelItem->GetItemID() ) )
+ {
+ if ( pItemPanel->GetItem() )
+ {
+ pszBorder = g_szItemBorders[iRarity][3];
+ pItemPanel->SetGreyedOut( NULL );
+ }
+ else
+ {
+ pszBorder = g_szItemBorders[iRarity][0];
+ }
+ }
+ else // Look gloomy and uninviting if not
+ {
+ pszBorder = g_szItemBorders[iRarity][3];
+ pItemPanel->SetGreyedOut( "" );
+ }
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) );
+ return;
+ }
+
+ }
+ else if( IsOutputPanel( iIndex ) )
+ {
+ const char *pszBorder = NULL;
+ int iRarity = GetItemQualityForBorder( pItemPanel );
+
+ // The output panel greys out when any of the input panels are not fulfilled,
+ // and lights up when they are all fulfilled
+ if ( iRarity >= 0 && iRarity < ARRAYSIZE( g_szItemBorders ) )
+ {
+ if( AllRecipePanelsFilled() )
+ {
+ pszBorder = g_szItemBorders[iRarity][0];
+ pItemPanel->SetGreyedOut( NULL );
+ }
+ else
+ {
+ pszBorder = g_szItemBorders[iRarity][3];
+ pItemPanel->SetGreyedOut( NULL );
+ }
+ }
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) );
+ return;
+ }
+
+ // Backpack and output panels get default treatment
+ BaseClass::SetBorderForItem( pItemPanel, bMouseOver );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Swap recipe items in srcpanel and dstpanel
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::SetRecipeComponentIntoPanel( itemid_t nSrcRecipeIndex, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage, CRecipeComponentItemModelPanel* pDstPanel, int nDstPage )
+{
+ Assert( nSrcRecipeIndex != 0 );
+ Assert( pSrcPanel );
+ Assert( pDstPanel );
+ Assert( pSrcPanel->GetRecipeItem( nSrcPage ) );
+
+ // Get the recipe from the destination panel
+ itemid_t pDstRecipeIndex = pDstPanel->GetRecipeIndex( nDstPage );
+ // Swap recipes
+ pSrcPanel->SetRecipeItem( pDstRecipeIndex, nSrcPage );
+ pDstPanel->SetRecipeItem( nSrcRecipeIndex, nDstPage );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check if a given panel is allowed to be dragged.
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::AllowDragging( CItemModelPanel *pPanel )
+{
+ int iIndex = GetBackpackPositionForPanel( pPanel );
+
+ // If this isn't an input or backpack panel, abort
+ if( !IsInputPanel( iIndex ) && !IsBackpackPanel( iIndex ) )
+ return false;
+
+ CRecipeComponentItemModelPanel* pRecipePanel = dynamic_cast< CRecipeComponentItemModelPanel* >( pPanel );
+ Assert( pRecipePanel );
+ if( !pRecipePanel )
+ return false;
+
+ int nPageNumber = 0;
+ if( IsBackpackPanel( iIndex ) )
+ {
+ nPageNumber = GetCurrentPage();
+ }
+ else if ( IsInputPanel( iIndex ) )
+ {
+ nPageNumber = m_nInputPage;
+ }
+ // Get the recipe item out of this panel
+ CEconItemView* pRecipe = pRecipePanel->GetRecipeItem( nPageNumber );
+
+ // If no recipe, abort
+ if( !pRecipe )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Start dragging!
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::StartDrag( int x, int y )
+{
+ BaseClass::StartDrag( x, y );
+
+ // Recalc the borders on item panels
+ FOR_EACH_VEC( m_pItemModelPanels, i )
+ {
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop dragging
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::StopDrag( bool bSucceeded )
+{
+ BaseClass::StopDrag( bSucceeded );
+
+ // Recalc the borders on item panels
+ FOR_EACH_VEC( m_pItemModelPanels, i )
+ {
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Helper function to find out if an input panel can accept an item
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::InputPanelCanAcceptItem( CItemModelPanel* pPanel, itemid_t nItemID )
+{
+ Assert( IsInputPanel( GetBackpackPositionForPanel( pPanel ) ) );
+
+ CInputPanelItemModelPanel* pInputPanel = dynamic_cast<CInputPanelItemModelPanel*>( pPanel );
+ Assert( pInputPanel );
+
+ return pInputPanel->MatchesAttribCriteria( nItemID );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check if the dragged item can be dropped into a given panel
+//-----------------------------------------------------------------------------
+bool CDynamicRecipePanel::CanDragTo( CItemModelPanel *pItemPanel, int iPanelIndex )
+{
+ // From input to backpack panel
+ int iDraggedFromPos = GetBackpackPositionForPanel(m_pItemDraggedFromPanel);
+ if( IsInputPanel( iDraggedFromPos ) && IsBackpackPanel( iPanelIndex ) )
+ return true;
+
+ itemid_t itemID = m_pMouseDragItemPanel->GetItem()
+ ? m_pMouseDragItemPanel->GetItem()->GetItemID()
+ : 0;
+
+ // From backpack to input panel that can accept the item
+ if( IsBackpackPanel( iDraggedFromPos )
+ && IsInputPanel( iPanelIndex )
+ && InputPanelCanAcceptItem( pItemPanel,itemID ) )
+ return true;
+
+ // From inoput to other input that can also accept the item
+ if( IsInputPanel( iDraggedFromPos )
+ && IsInputPanel( iPanelIndex )
+ && InputPanelCanAcceptItem( pItemPanel, itemID ) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle the user releasing their drag on a given panel. Only valid
+// from backpack<->backpack, input<->input, backpack<->input
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::HandleDragTo( CItemModelPanel * /*pItemPanel*/, int iPanelIndex )
+{
+ int iDraggedFromPos = GetBackpackPositionForPanel(m_pItemDraggedFromPanel);
+
+ // The destination panel needs to exist, be enabled and be visible or else we dont consider it.
+ CItemModelPanel* pDestinationPanel = m_pItemModelPanels[ iPanelIndex ];
+ if( !pDestinationPanel || !pDestinationPanel->IsEnabled() || !pDestinationPanel->IsVisible() )
+ {
+ return;
+ }
+
+ m_bInputPanelsDirty = true;
+
+ // If we dragged from a inventory panel onto an input panel
+ if( IsInputPanel( iPanelIndex ) && IsBackpackPanel( iDraggedFromPos ) )
+ {
+ CRecipeComponentItemModelPanel* pSrcPanel = dynamic_cast<CRecipeComponentItemModelPanel*>( m_pItemModelPanels[iDraggedFromPos] );
+ CInputPanelItemModelPanel* pDstPanel = dynamic_cast<CInputPanelItemModelPanel*>( m_pItemModelPanels[iPanelIndex] );
+
+ if( pSrcPanel && pDstPanel )
+ {
+ // They dragged an item from the backpack into an input slot. Check if this is ok.
+ itemid_t nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( GetCurrentPage() );
+ Assert( nSrcRecipeIndex != 0 );
+ if( nSrcRecipeIndex != 0 && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) )
+ {
+ SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, GetCurrentPage(), pDstPanel, pDstPanel->GetPageNumber() );
+ }
+ }
+ }
+ else if( IsInputPanel( iDraggedFromPos ) && IsBackpackPanel( iPanelIndex ) )
+ {
+ CRecipeComponentItemModelPanel* pSrcPanel = dynamic_cast<CRecipeComponentItemModelPanel*>( m_pItemModelPanels[iDraggedFromPos] );
+ CRecipeComponentItemModelPanel* pDstPanel = dynamic_cast<CRecipeComponentItemModelPanel*>( m_pItemModelPanels[iPanelIndex] );
+
+ // If we dragged from an input panel to a backpack panel, return the item
+ // to its original backpack slot regardless of where they dragged it to
+ if( pSrcPanel && pDstPanel )
+ {
+ // They dragged from an input panel to inventory panel. Check if there's something in
+ // the inventory slot already. If so, just move to the first open spot.
+ itemid_t pSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pSrcPanel->GetPageNumber() );
+ itemid_t pDstRecipeIndex = pDstPanel->GetRecipeIndex( GetCurrentPage() );
+
+ Assert( pSrcRecipeIndex != 0 );
+ if( pSrcRecipeIndex != 0 && pDstRecipeIndex != 0 )
+ {
+ // There's already a recipe item in there. Just put it in the first open slot.
+ ReturnRecipeItemToBackpack( pSrcRecipeIndex, pSrcPanel, 0 );
+ }
+ else if( pSrcRecipeIndex )
+ {
+ // It's open! Place in there
+ SetRecipeComponentIntoPanel( pSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, GetCurrentPage() );
+ }
+ }
+ }
+ else if( IsInputPanel( iDraggedFromPos ) && IsInputPanel( iPanelIndex ) )
+ {
+ CInputPanelItemModelPanel* pSrcPanel = dynamic_cast<CInputPanelItemModelPanel*>( m_pItemModelPanels[iDraggedFromPos] );
+ CInputPanelItemModelPanel* pDstPanel = dynamic_cast<CInputPanelItemModelPanel*>( m_pItemModelPanels[iPanelIndex] );
+
+ // Dragged from an input panel to an input panel.
+ if( pSrcPanel && pDstPanel )
+ {
+ itemid_t nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pSrcPanel->GetPageNumber() );
+ itemid_t nDstRecipeIndex = pDstPanel->GetRecipeIndex( pDstPanel->GetPageNumber() );
+
+ // First see if we can swap our inputs
+ if( nSrcRecipeIndex != 0 && nDstRecipeIndex != 0 )
+ {
+ if( pSrcPanel->MatchesAttribCriteria( nDstRecipeIndex ) &&
+ pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) )
+ {
+ SetRecipeComponentIntoPanel( nDstRecipeIndex, pSrcPanel, 0, pDstPanel, 0 );
+ }
+ }
+ else if( nSrcRecipeIndex != 0 && nDstRecipeIndex == 0 && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) )
+ {
+ // Next check if we just want to move the item
+ SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, pDstPanel->GetPageNumber() );
+ }
+ }
+ }
+ else
+ {
+ AssertMsg( 0, "Unhandled drag case!" );
+ }
+
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return a given item to the first open slot in out backpack
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::ReturnRecipeItemToBackpack( itemid_t nItemID, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage )
+{
+ // For each page, check each recipe slot, on each panel for an opening
+ for( int i=0; i < GetNumPages(); ++i )
+ {
+ FOR_EACH_VEC( m_vecBackpackModelPanels, j )
+ {
+ CRecipeComponentItemModelPanel* pDstPanel = m_vecBackpackModelPanels[j];
+ CEconItemView* pDstRecipe = pDstPanel->GetRecipeItem( i );
+ bool bSlotIsOpen = pDstPanel->IsSlotAvailable( i );
+ if( bSlotIsOpen && pDstRecipe == NULL )
+ {
+ SetRecipeComponentIntoPanel( nItemID, pSrcPanel, nSrcPage, pDstPanel, i );
+ return;
+ }
+ }
+ }
+
+ AssertMsg( 0, "No open backpack slot found when returning item to backpack!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set in a new recipe item. Update labels and reset panels.
+//-----------------------------------------------------------------------------
+void CDynamicRecipePanel::SetNewRecipe( CEconItemView* pNewRecipeItem )
+{
+ m_flAbortCraftingAt = 0.f;
+ m_pDynamicRecipeItem = pNewRecipeItem;
+ // Update recipe title
+ m_pRecipeContainer->SetDialogVariable( "recipetitle", m_pDynamicRecipeItem->GetItemName() );
+ // By default, dont show untradable items
+ m_pShowUntradableItemsCheckbox->SetSelected( false );
+ // Repopulate the panels
+ InitItemPanels();
+ UpdateModelPanels();
+}
+
+
+class CWaitForConsumeDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitForConsumeDialog( 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 );
+ }
+};
+
+void CDynamicRecipePanel::OnCraftResponse( itemid_t nNewToolID, EGCMsgResponse eResponse )
+{
+ // We got a response. We dont need to time-out
+ m_flAbortCraftingAt = 0;
+
+ switch( eResponse )
+ {
+ case k_EGCMsgResponseOK:
+ {
+ // If a new tool id comes back, that means we only partially completed the recipe
+ if( nNewToolID != 0 )
+ {
+ OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Success", false, true, false );
+
+ // Play a sound letting them know the item is gone
+ const char *pszSoundFilename = m_pDynamicRecipeItem->GetDefinitionString( "recipe_partial_complete_sound", "ui/chem_set_add_element.wav" );
+ vgui::surface()->PlaySound( pszSoundFilename );
+
+ CEconItemView* pNewRecipe = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( nNewToolID );
+ Assert( pNewRecipe );
+ // Set the new recipe into the panel. This resets all the item panels within.
+ SetNewRecipe( pNewRecipe );
+ }
+ else
+ {
+ CloseCraftingStatusDialog();
+
+ // No new tool, so we completed the recipe!
+ const char *pszSoundFilename = m_pDynamicRecipeItem->GetDefinitionString( "recipe_complete_sound", "ui/chem_set_creation.wav" );
+ vgui::surface()->PlaySound( pszSoundFilename );
+ // Show the "Completing consumption" dialog for 5 seconds
+ ShowWaitingDialog( new CWaitForConsumeDialog( NULL ), "#ToolConsumptionInProgress", true, false, 5.0f );
+
+ // Hide the dynamic recipe panel after 6 seconds
+ g_DynamicRecipePanel->PostMessage( g_DynamicRecipePanel, new KeyValues("RecipeCompleted"), 6.f );
+ }
+ }
+ break;
+ case k_EGCMsgResponseInvalid:
+ {
+ // Something was bad in the request.
+ OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Invalid", false, true, false );
+
+ InitItemPanels();
+ UpdateModelPanels();
+ }
+ break;
+ case k_EGCMsgResponseNoMatch:
+ {
+ // One or more of the items sent in didnt match
+ OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_NoMatch", false, true, false );
+
+ InitItemPanels();
+ UpdateModelPanels();
+ }
+ break;
+ default:
+ {
+ OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Default", false, true, false );
+
+ InitItemPanels();
+ UpdateModelPanels();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the dynamic recipe
+//-----------------------------------------------------------------------------
+class CGCCompleteDynamicRecipeResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCCompleteDynamicRecipeResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ itemid_t nNewToolID = INVALID_ITEM_ID;
+ if( !msg.BReadUint64Data( &nNewToolID ) )
+ return true;
+
+ g_DynamicRecipePanel->OnCraftResponse( nNewToolID, (EGCMsgResponse)msg.Body().m_eResponse );
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCCompleteDynamicRecipeResponse, "CGCCompleteDynamicRecipeResponse", k_EMsgGCFulfillDynamicRecipeComponentResponse, GCSDK::k_EServerTypeGCClient );
+
+#ifdef STAGING_ONLY
+void CDynamicRecipePanel::Debug_GiveRequiredInputs() const
+{
+ if ( !steamapicontext || !steamapicontext->SteamUser() )
+ {
+ Msg("Not connected to Steam.\n");
+ return;
+ }
+ CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID();
+ if ( !steamIDForPlayer.IsValid() )
+ {
+ Msg("Failed to find a valid steamID for the local player.\n");
+ return;
+ }
+
+ FOR_EACH_VEC( m_vecRecipeInputModelPanels, i )
+ {
+ CInputPanelItemModelPanel *pInputPanel = m_vecRecipeInputModelPanels[i];
+
+ int nPage = 0;
+ for( const CEconItemAttributeDefinition *pAttrDef = pInputPanel->GetAttrib( nPage ); pAttrDef != NULL; pAttrDef = pInputPanel->GetAttrib( ++nPage ) )
+ {
+ CAttribute_DynamicRecipeComponent attribValue;
+ if( m_pDynamicRecipeItem->FindAttribute<CAttribute_DynamicRecipeComponent >( pAttrDef, &attribValue ) )
+ {
+ GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest );
+ msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() );
+ CItemSelectionCriteria criteria;
+
+ if( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET )
+ {
+ criteria.BAddCondition( "name", k_EOperator_String_EQ, GetItemSchema()->GetItemDefinition( attribValue.def_index() )->GetDefinitionName(), true );
+ }
+
+ if( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_QUALITY_SET )
+ {
+ criteria.SetQuality( attribValue.item_quality() );
+ }
+
+ criteria.SetIgnoreEnabledFlag( true );
+ criteria.BSerializeToMsg( *msg.Body().mutable_criteria() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ }
+ }
+}
+#endif
diff --git a/game/client/tf/vgui/dynamic_recipe_subpanel.h b/game/client/tf/vgui/dynamic_recipe_subpanel.h
new file mode 100644
index 0000000..f0bddaa
--- /dev/null
+++ b/game/client/tf/vgui/dynamic_recipe_subpanel.h
@@ -0,0 +1,267 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef DYNAMIC_RECIPE_SUBPANEL_H
+#define DYNAMIC_RECIPE_SUBPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "backpack_panel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "tf_gcmessages.h"
+#include "econ_gcmessages.h"
+#include "tf_imagepanel.h"
+#include "tf_controls.h"
+#include "item_selection_panel.h"
+#include "econ_dynamic_recipe.h"
+
+class CImageButton;
+
+#define DYNAMIC_RECIPE_INPUT_ROWS 4
+#define DYNAMIC_RECIPE_INPUT_COLS 3
+#define DYNAMIC_RECIPE_INPUT_COUNT ( DYNAMIC_RECIPE_INPUT_ROWS * DYNAMIC_RECIPE_INPUT_COLS )
+#define DYNAMIC_RECIPE_OUTPUT_ROWS 4
+#define DYNAMIC_RECIPE_OUTPUT_COLS 3
+#define DYNAMIC_RECIPE_OUTPUT_COUNT ( DYNAMIC_RECIPE_OUTPUT_ROWS * DYNAMIC_RECIPE_OUTPUT_COLS )
+
+#define DYNAMIC_RECIPE_BACKPACK_ROWS 4
+#define DYNAMIC_RECIPE_BACKPACK_COLS 4
+#define DYNAMIC_RECIPE_PACKPACK_COUNT_PER_PAGE ( DYNAMIC_RECIPE_BACKPACK_ROWS * DYNAMIC_RECIPE_BACKPACK_COLS )
+
+class CRecipeComponentItemModelPanel;
+
+class CRecipeComponentItemModelPanel : public CItemModelPanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CRecipeComponentItemModelPanel, CItemModelPanel );
+ CRecipeComponentItemModelPanel( vgui::Panel *parent, const char *name );
+
+ void AddRecipe( itemid_t nRecipe );
+ virtual void DeleteRecipes();
+ virtual void SetItem( const CEconItemView *pItem ) OVERRIDE;
+ void SetRecipeItem( itemid_t nRecipeItem, int nPageNumber );
+ void AddDefaultItem( CEconItemView *pItem );
+ CEconItemView* GetRecipeItem( int nPageNumber ) const;
+ itemid_t GetRecipeIndex( int nPageNumber ) const;
+ bool IsSlotAvailable( int nPageNumber );
+ CEconItemView* GetDefaultItem() const { return m_nPageNumber < m_vecDefaultItems.Count() ? m_vecDefaultItems[ m_nPageNumber ] : NULL; }
+ void UpdateDisplayItem();
+
+ void SetPageNumber( int nPageNumber );
+ int GetPageNumber() const { return m_nPageNumber; }
+protected:
+ struct RecipeItem_t
+ {
+ itemid_t m_nRecipeIndex;
+ CEconItemView* m_pRecipeItem;
+ };
+
+ void UpdateRecipeItem( RecipeItem_t* pRecipeItem );
+ virtual void SetBlankState();
+
+ CUtlVector< CEconItemView* > m_vecDefaultItems;
+ CUtlVector< RecipeItem_t > m_vecRecipes;
+ int m_nPageNumber;
+};
+
+class CInputPanelItemModelPanel : public CRecipeComponentItemModelPanel
+{
+public:
+ CInputPanelItemModelPanel( vgui::Panel *parent, const char *name, const CEconItemView* pDynamicRecipeItem )
+ : CRecipeComponentItemModelPanel( parent, name )
+ , m_pDynamicRecipeItem( pDynamicRecipeItem )
+ {}
+
+ virtual void DeleteRecipes();
+ void AddComponentInfo( const CEconItemAttributeDefinition *pComponentAttrib );
+ bool MatchesAttribCriteria( itemid_t itemID ) const;
+ bool MatchesAttribCriteria( itemid_t itemID, int nPageNumber ) const;
+ const CEconItemAttributeDefinition * GetAttrib( int nPageNumber ) const;
+ void SetDynamicRecipeItem( const CEconItemView* pDynamicRecipeItem ) { m_pDynamicRecipeItem = pDynamicRecipeItem; }
+
+protected:
+ virtual void SetBlankState() OVERRIDE;
+
+private:
+ CUtlVector< const CEconItemAttributeDefinition* > m_vecAttrDef;
+ const CEconItemView* m_pDynamicRecipeItem;
+};
+
+
+//-----------------------------------------------------------------------------
+// An inventory screen that handles displaying the crafting screen
+//-----------------------------------------------------------------------------
+class CDynamicRecipePanel : public CBackpackPanel
+{
+ DECLARE_CLASS_SIMPLE( CDynamicRecipePanel, CBackpackPanel );
+public:
+
+#ifdef STAGING_ONLY
+ void Debug_GiveRequiredInputs() const;
+ CExButton *m_pDevGiveInputsButton;
+#endif
+
+ CDynamicRecipePanel( vgui::Panel *parent, const char *panelName, CEconItemView* pRecipeItem );
+ ~CDynamicRecipePanel( void );
+
+ void SetNewRecipe( CEconItemView* pNewRecipeItem );
+ void ConsumeItem( );
+ void InitItemPanels();
+ virtual const char *GetResFile( void ) { return "Resource/UI/DynamicRecipePanel.res"; }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE;
+ void OnButtonChecked( KeyValues *pData ) OVERRIDE;
+
+ virtual void OpenContextMenu() OVERRIDE {}
+ virtual int GetNumItemPanels( void ) OVERRIDE;
+ virtual void AddNewItemPanel( int iPanelIndex ) OVERRIDE;
+ void Craft();
+ virtual void OnTick( void ) OVERRIDE;
+ virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory ) OVERRIDE;
+ void OnCraftResponse( itemid_t nNewToolID, EGCMsgResponse eResponse );
+private:
+
+ bool IsInputPanel( int iPanelIndex ) const;
+ bool IsOutputPanel( int iPanelIndex) const;
+ bool IsBackpackPanel( int iPanelIndex) const;
+ bool IsInvPanelOnThisPage( unsigned nIndex ) const;
+ int GetNumBackpackPanelsPerPage() const { return DYNAMIC_RECIPE_BACKPACK_ROWS * DYNAMIC_RECIPE_BACKPACK_COLS; }
+ virtual int GetNumPages() OVERRIDE;
+ virtual void SetCurrentPage( int nNewPage ) OVERRIDE;
+ int GetFirstBackpackIndex() const { return DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT; }
+
+ void SetCurrentInputPage( int nNewPage );
+ int GetNumInputPages() const;
+ int GetNumInputPanelsPerPage() const { return DYNAMIC_RECIPE_INPUT_COUNT; }
+
+ int GetNumOutputPage() const;
+ int GetNumOutputPanelsPerPage() const { return DYNAMIC_RECIPE_OUTPUT_COUNT; }
+
+ class CRecipeComponentAttributeCounter : public CEconItemSpecificAttributeIterator
+ {
+ public:
+ CRecipeComponentAttributeCounter()
+ : m_nInputCount( 0 )
+ {}
+ ~CRecipeComponentAttributeCounter() { Reset(); }
+
+ virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) OVERRIDE;
+ int GetInputCount() const { return m_nInputCount; }
+ int GetOutputCount() const { return m_vecOutputItems.Count(); }
+ CEconItemView* GetOutputItem( int i );
+ CEconItemView* GetInputItem( int i );
+ const CEconItemAttributeDefinition* GetInputAttrib( int i );
+
+ void Reset();
+
+ private:
+
+ struct InputComponent_t
+ {
+ CEconItemView m_ItemView;
+ const CEconItemAttributeDefinition* m_pAttrib;
+ };
+
+ typedef CUtlVector< CCopyableUtlVector<InputComponent_t> > InputComponentVec;
+
+ static int LeastCommonInputSortFunc( const CCopyableUtlVector<InputComponent_t> *p1, const CCopyableUtlVector<InputComponent_t> *p2 );
+ InputComponent_t* GetInputComponent( int i );
+
+ InputComponentVec m_vecInputItems;
+ CUtlVector< CEconItemView > m_vecOutputItems;
+ CUtlVector< CEconItem* > m_vecTempEconItems;
+ int m_nInputCount;
+ };
+
+ class CDynamicRecipeItemMatchFind : public CEconItemSpecificAttributeIterator
+ {
+ public:
+ CDynamicRecipeItemMatchFind( const CEconItemView* pSourceItem, const CEconItemView* pItemTomatch )
+ : m_bMatchesAny( false )
+ , m_pSourceItem( pSourceItem )
+ , m_pItemToMatch( pItemTomatch )
+ {}
+
+ virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) OVERRIDE;
+ bool MatchesAnyAttributes() const { return m_bMatchesAny; }
+ private:
+ const CEconItemView* m_pSourceItem;
+ const CEconItemView* m_pItemToMatch;
+ bool m_bMatchesAny;
+ };
+
+ CEconItemView* m_pDynamicRecipeItem;
+ CRecipeComponentAttributeCounter m_RecipeIterator;
+
+ bool AllRecipePanelsFilled( void );
+ bool CheckForUntradableItems( void );
+ bool WarnAboutPartialCompletion( void );
+ void FindPossibleBackpackItems();
+ virtual void PositionItemPanel( CItemModelPanel *pPanel, int iIndex );
+ void PopulatePanelsForCurrentPage();
+ virtual void UpdateModelPanels( void );
+ virtual void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver );
+ void SetRecipeComponentIntoPanel( itemid_t nSrcRecipeIndex, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage, CRecipeComponentItemModelPanel* pDstPanel, int nDstPage );
+ bool InputPanelCanAcceptItem( CItemModelPanel* pPanel, itemid_t nItemID );
+
+ CTFTextToolTip *m_pToolTip;
+ vgui::EditablePanel *m_pToolTipEmbeddedPanel;
+ CExButton *m_pRecipeCraftButton;
+ CExLabel *m_pNoMatchesLabel;
+ CExLabel *m_pUntradableOutputsLabel;
+ CExLabel *m_pInputsLabel;
+ CExLabel *m_pOutputsLabel;
+ vgui::Label *m_pCurInputPageLabel;
+ CExButton *m_pNextInputPageButton;
+ CExButton *m_pPrevInputPageButton;
+ CItemModelPanel *m_pMouseOverItemPanel;
+ vgui::CheckButton *m_pShowUntradableItemsCheckbox;
+
+ CUtlVector<CInputPanelItemModelPanel*> m_vecRecipeInputModelPanels;
+ CUtlVector<CRecipeComponentItemModelPanel*> m_vecBackpackModelPanels;
+ CUtlVector<CItemModelPanel*> m_vecRecipeOutputModelPanels;
+
+ vgui::EditablePanel *m_pRecipeContainer;
+ vgui::EditablePanel *m_pInventoryContainer;
+
+ unsigned m_nNumRecipeItems;
+ bool m_bAllRecipePanelsFilled;
+ bool m_bInputPanelsDirty;
+ bool m_bShowUntradable;
+
+ int m_nInputPage;
+ int m_nOutputPage;
+
+ float m_flAbortCraftingAt;
+
+ MESSAGE_FUNC_PTR( OnItemPanelMouseDoublePressed, "ItemPanelMouseDoublePressed", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel );
+ MESSAGE_FUNC( OnRecipeCompleted, "RecipeCompleted" );
+
+ virtual bool AllowDragging( CItemModelPanel *panel ) OVERRIDE;
+ virtual void StartDrag( int x, int y ) OVERRIDE;
+ virtual void StopDrag( bool bSucceeded ) OVERRIDE;
+ virtual bool CanDragTo( CItemModelPanel *pItemPanel, int iPanelIndex ) OVERRIDE;
+ virtual void HandleDragTo( CItemModelPanel *pItemPanel, int iPanelIndex ) OVERRIDE;
+
+ void ReturnRecipeItemToBackpack( itemid_t nItemID, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage );
+
+ CPanelAnimationVarAliasType( int, m_iItemCraftingOffcenterX, "item_crafting_offcenter_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iOutputItemYPos, "output_item_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iInventoryXPos, "inventory_xpos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iInventoryYPos, "inventory_ypos", "0", "proportional_int" );
+
+ friend void ConfirmDestroyItems( bool bConfirmed, void* pContext );
+};
+
+#endif // DYNAMIC_RECIPE_SUBPANEL_H
diff --git a/game/client/tf/vgui/halloween_offering_panel.cpp b/game/client/tf/vgui/halloween_offering_panel.cpp
new file mode 100644
index 0000000..9cc3d1c
--- /dev/null
+++ b/game/client/tf/vgui/halloween_offering_panel.cpp
@@ -0,0 +1,88 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "halloween_offering_panel.h"
+#include "cdll_client_int.h"
+#include "ienginevgui.h"
+#include "econ_item_tools.h"
+#include "econ_ui.h"
+#include <vgui_controls/AnimationController.h>
+#include "clientmode_tf.h"
+#include "softline.h"
+#include "drawing_panel.h"
+#include "tf_item_inventory.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CHalloweenOfferingPanel::CHalloweenOfferingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip )
+ : BaseClass( parent, pTooltip )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CHalloweenOfferingPanel::~CHalloweenOfferingPanel( void )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+void CHalloweenOfferingPanel::CreateSelectionPanel()
+{
+ CHalloweenOfferingSelectionPanel *pSelectionPanel = new CHalloweenOfferingSelectionPanel( this );
+ m_hSelectionPanel = (CCollectionCraftingSelectionPanel*)pSelectionPanel;
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHalloweenOfferingPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( "envelopesend", command ) )
+ {
+ GCSDK::CProtoBufMsg<CMsgCraftHalloweenOffering> msg( k_EMsgGCCraftHalloweenOffering );
+
+ // Find the Garygoyle 'tool' item for this
+ static CSchemaItemDefHandle pItemDef_Gargoyle( "Activated Halloween Pass" );
+ Assert( pItemDef_Gargoyle );
+ if ( !pItemDef_Gargoyle )
+ return;
+ // Find out if the user owns this item or not and place in the proper bucket
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+ if ( !pLocalInv )
+ return;
+
+ const CEconItemView *pRefItem = pLocalInv->FindFirstItembyItemDef( pItemDef_Gargoyle->GetDefinitionIndex() );
+ if ( !pRefItem )
+ return;
+
+ msg.Body().set_tool_id( pRefItem->GetItemID() );
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[ i ]->GetItem() == NULL )
+ return;
+
+ msg.Body().add_item_id( m_vecItemPanels[ i ]->GetItem()->GetItemID() );
+ }
+ // Send if off
+ GCClientSystem()->BSendMessage( msg );
+
+ m_bWaitingForGCResponse = true;
+ m_nFoundItemID.Purge();
+ m_timerResponse.Start( 5.f );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "CollectionCrafting_LetterSend" );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/halloween_offering_panel.h b/game/client/tf/vgui/halloween_offering_panel.h
new file mode 100644
index 0000000..cc4c092
--- /dev/null
+++ b/game/client/tf/vgui/halloween_offering_panel.h
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef HALLOWEEN_OFFERING_PANEL_H
+#define HALLOWEEN_OFFERING_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "collection_crafting_panel.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CHalloweenOfferingSelectionPanel : public CCollectionCraftingSelectionPanel
+{
+ DECLARE_CLASS_SIMPLE( CHalloweenOfferingSelectionPanel, CCollectionCraftingSelectionPanel );
+public:
+ CHalloweenOfferingSelectionPanel( Panel *pParent ) : BaseClass( pParent ) {}
+
+ //-----------------------------------------------------------------------------
+ virtual const char *GetSelectionInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) const
+ {
+ return GetHalloweenOfferingInvalidReason( pTestItem, pSourceItem );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// A panel to let users choose 10 weapons to craft up within collections
+//-----------------------------------------------------------------------------
+class CHalloweenOfferingPanel : public CCollectionCraftingPanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CHalloweenOfferingPanel, CCollectionCraftingPanel );
+ CHalloweenOfferingPanel( vgui::Panel *parent, CItemModelPanelToolTip* pTooltip );
+ ~CHalloweenOfferingPanel( void );
+
+ virtual const char *GetResFile( void ) { return "Resource/UI/econ/HalloweenOfferingDialog.res"; }
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ virtual int GetInputItemCount() { return HALLOWEEN_OFFERING_ITEM_COUNT; }
+
+protected:
+
+ virtual void CreateSelectionPanel();
+};
+
+#endif // HALLOWEEN_OFFERING_PANEL_H
diff --git a/game/client/tf/vgui/item_ad_panel.cpp b/game/client/tf/vgui/item_ad_panel.cpp
new file mode 100644
index 0000000..75206cc
--- /dev/null
+++ b/game/client/tf/vgui/item_ad_panel.cpp
@@ -0,0 +1,489 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "item_ad_panel.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "econ_store.h"
+#include "econ_ui.h"
+#include "store/store_panel.h"
+#include "tf_controls.h"
+#include "econ_item_description.h"
+#include "vgui/IInput.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseAdPanel::CBaseAdPanel( Panel *parent, const char *panelName )
+ : BaseClass( parent, panelName )
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseAdPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_flPresentTime = inResourceData->GetFloat( "present_time", 10.f );
+}
+
+bool CBaseAdPanel::CheckForRequiredSteamComponents( const char* pszSteamRequried, const char* pszOverlayRequired )
+{
+ // Make sure we've got the appropriate connections to Steam
+ if ( !steamapicontext || !steamapicontext->SteamUtils() )
+ {
+ OpenStoreStatusDialog( NULL, pszSteamRequried, true, false );
+ return false;
+ }
+
+ if ( !steamapicontext->SteamUtils()->IsOverlayEnabled() )
+ {
+ OpenStoreStatusDialog( NULL, pszOverlayRequired, true, false );
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemAdPanel::CItemAdPanel( Panel *parent, const char *panelName, item_definition_index_t itemDefIndex )
+ : BaseClass( parent, panelName )
+ , m_ItemDefIndex( itemDefIndex )
+ , m_bShowMarketButton( true )
+{
+ SetDialogVariable( "price", "..." );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemAdPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetItemDef()->GetAdResFile() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemAdPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_bShowMarketButton = inResourceData->GetBool( "show_market", true ); // Default to showing market
+
+ if ( !m_bShowMarketButton )
+ {
+ // Tick every second as we try to get our price from the store
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 1000 );
+ }
+
+ const CTFItemDefinition* pItemDef = GetItemDef();
+ CItemModelPanel* pItemImage = FindControl< CItemModelPanel >( "ItemIcon" );
+ if ( pItemImage )
+ {
+ CEconItemView adItem;
+ adItem.Init( pItemDef->GetDefinitionIndex(), AE_UNIQUE, 1, 1 );
+ pItemImage->InvalidateLayout( true, true );
+ pItemImage->SetItem( &adItem );
+
+ KeyValuesAD modelpanelKV( "modelpanel_kv" );
+ KeyValues *itemKV = new KeyValues( "itemmodelpanel" );
+ itemKV->SetBool( "inventory_image_type", true );
+ itemKV->SetBool( "use_item_rendertarget", false );
+ itemKV->SetBool( "allow_rot", false );
+
+ modelpanelKV->AddSubKey( itemKV );
+ pItemImage->ApplySettings( modelpanelKV );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemAdPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ const CTFItemDefinition* pItemDef = GetItemDef();
+
+ // Get the ad text for the item. If it's not there, juse use the description text.
+ SetDialogVariable( "item_name", g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ) );
+ const char* pszAdtext = pItemDef->GetAdTextToken() ? pItemDef->GetAdTextToken() : pItemDef->GetItemDesc();
+ CExScrollingEditablePanel* pScrollableItemText = FindControl< CExScrollingEditablePanel >( "ScrollableItemText", true );
+ if ( pszAdtext && pScrollableItemText )
+ {
+ pScrollableItemText->SetDialogVariable( "item_ad_text", g_pVGuiLocalize->Find( pszAdtext ) );
+
+ Label* pAdLabel = pScrollableItemText->FindControl< Label >( "ItemAdText", true );
+ if ( pAdLabel )
+ {
+ int nWide, nTall;
+ pAdLabel->GetContentSize( nWide, nTall );
+ pAdLabel->SetTall( nTall );
+ }
+
+ pScrollableItemText->InvalidateLayout( true );
+ }
+
+ CExButton* pBuyButton = FindControl< CExButton >( "BuyButton", true );
+ CExButton* pMarketButton = FindControl< CExButton >( "MarketButton", true );
+ if ( pBuyButton && pMarketButton )
+ {
+
+ pBuyButton->SetVisible( !m_bShowMarketButton );
+ pMarketButton->SetVisible( m_bShowMarketButton );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemAdPanel::OnTick()
+{
+ const CTFItemDefinition* pItemDef = GetItemDef();
+ bool bStoreIsReady = EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser() && pItemDef;
+ if ( bStoreIsReady )
+ {
+ // Get the price of the item
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pItemDef->GetDefinitionIndex() );
+ if ( pEntry )
+ {
+ item_price_t unPrice = pEntry->GetCurrentPrice( eCurrency );
+ // Set that price into the button
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), unPrice, eCurrency );
+ SetDialogVariable( "price", wzLocalizedPrice );
+
+ // Don't need to tick anymore
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const CTFItemDefinition* CItemAdPanel::GetItemDef() const
+{
+ return (CTFItemDefinition*)ItemSystem()->GetItemSchema()->GetItemDefinition( m_ItemDefIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemAdPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( "purchase", command ) )
+ {
+ if ( !CheckForRequiredSteamComponents( "#StoreUpdate_SteamRequired", "#MMenu_OverlayRequired" ) )
+ return;
+
+ const CTFItemDefinition* pItemDef = GetItemDef();
+ if ( pItemDef )
+ {
+ if ( EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser() )
+ {
+ // Add a the item to the users cart and checkout
+ EconUI()->GetStorePanel()->GetCart()->EmptyCart();
+ AddItemToCartHelper( NULL, pItemDef->GetDefinitionIndex(), kCartItem_Purchase );
+ EconUI()->GetStorePanel()->InitiateCheckout( true );
+ }
+ }
+ }
+ else if ( FStrEq( "market", command ) )
+ {
+ if ( !CheckForRequiredSteamComponents( "#StoreUpdate_SteamRequired", "#MMenu_OverlayRequired" ) )
+ return;
+
+ const CTFItemDefinition* pItemDef = GetItemDef();
+ if ( pItemDef && steamapicontext && steamapicontext->SteamFriends() )
+ {
+ const char *pszPrefix = "";
+ if ( GetUniverse() == k_EUniverseBeta )
+ {
+ pszPrefix = "beta.";
+ }
+
+ static char pszItemName[256];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find ( pItemDef->GetItemBaseName() ) , pszItemName, sizeof(pszItemName) );
+
+ char szURL[512];
+ V_snprintf( szURL, sizeof(szURL), "http://%ssteamcommunity.com/market/listings/%d/%s", pszPrefix, engine->GetAppID(), pszItemName );
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL );
+ }
+ }
+}
+
+
+
+DECLARE_BUILD_FACTORY( CCyclingAdContainerPanel );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCyclingAdContainerPanel::CCyclingAdContainerPanel( Panel *parent, const char *panelName )
+ : BaseClass( parent, panelName )
+ , m_pAdsContainer( NULL )
+ , m_pKVItems( NULL )
+ , m_nCurrentIndex( 0 )
+ , m_nXPos( 0 )
+ , m_nTargetIndex( 0 )
+ , m_nTransitionStartOffsetX( 0 )
+ , m_bTransitionRight( true )
+ , m_bSettingsApplied( false )
+ , m_bNeedsToCreatePanels( false )
+{
+ m_pAdsContainer = new EditablePanel( this, "AdsContainer" );
+ m_pFadePanel = new EditablePanel( this, "FadeTransition" );
+ m_pNextButton = new CExButton( this, "NextButton", ">", this, "next" );
+ m_pPrevButton = new CExButton( this, "PrevButton", "<", this, "prev" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCyclingAdContainerPanel::~CCyclingAdContainerPanel()
+{
+ if ( m_pKVItems )
+ {
+ m_pKVItems->deleteThis();
+ m_pKVItems = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/econ/CyclingAdContainer.res" );
+
+ m_bSettingsApplied = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues* pKVItems = inResourceData->FindKey( "items" );
+ if ( pKVItems )
+ {
+ SetItemKVs( pKVItems );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::CreatePanels()
+{
+ if ( ItemSystem()->GetItemSchema()->GetVersion() == 0 )
+ return;
+
+ m_vecPossibleAds.Purge();
+
+ FOR_EACH_TRUE_SUBKEY( m_pKVItems, pKVItem )
+ {
+ const char* pszItemName = pKVItem->GetString( "item" );
+ const CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName( pszItemName );
+ if ( pDef )
+ {
+ AdData_t& adData = m_vecPossibleAds[ m_vecPossibleAds.AddToTail() ];
+ adData.m_pAdPanel = new CItemAdPanel( m_pAdsContainer, "ad", pDef->GetDefinitionIndex() );
+
+ adData.m_pAdPanel->InvalidateLayout( true, true ); // Default settings
+ adData.m_pAdPanel->ApplySettings( pKVItem );
+ adData.m_pAdPanel->InvalidateLayout();
+ }
+ else
+ {
+ AssertMsg( 0, "Invalid item def '%s'!", pszItemName );
+ }
+ }
+
+ m_bNeedsToCreatePanels = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ PresentIndex( 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( m_bNeedsToCreatePanels && m_bSettingsApplied )
+ {
+ CreatePanels();
+ PresentIndex( 0 );
+ }
+
+ UpdateAdPanelPositions();
+
+ // See if it's time to auto-cycle to the next ad
+ if ( m_ShowTimer.HasStarted() && m_ShowTimer.IsElapsed() && m_vecPossibleAds.Count() > 1 )
+ {
+ m_ShowTimer.Invalidate();
+ PresentIndex( m_nTargetIndex + 1 );
+ }
+
+ int nMouseX, nMouseY;
+ vgui::input()->GetCursorPos( nMouseX, nMouseY );
+ bool bControlsVisible = IsWithin( nMouseX, nMouseY ) && m_vecPossibleAds.Count() > 1;
+ m_pPrevButton->SetVisible( bControlsVisible );
+ m_pNextButton->SetVisible( bControlsVisible );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::SetItemKVs( KeyValues* pKVItems )
+{
+ if ( pKVItems )
+ {
+ if ( m_pKVItems )
+ {
+ m_pKVItems->deleteThis();
+ m_pKVItems = NULL;
+ }
+ m_pKVItems = pKVItems->MakeCopy();
+ }
+
+ m_bNeedsToCreatePanels = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( "next", command ) )
+ {
+ PresentIndex( m_nTargetIndex + 1 );
+ }
+ else if ( FStrEq( "prev", command ) )
+ {
+ PresentIndex( m_nTargetIndex - 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::PresentIndex( int nIndex )
+{
+ if ( m_vecPossibleAds.IsEmpty() )
+ return;
+
+ if ( m_nCurrentIndex == nIndex )
+ return;
+
+ // Figure out which way we want to ransition
+ m_bTransitionRight = nIndex > m_nCurrentIndex;
+
+ // Wrap if needed
+ if ( nIndex >= m_vecPossibleAds.Count() )
+ {
+ nIndex = 0;
+ }
+ else if ( nIndex < 0 )
+ {
+ nIndex = m_vecPossibleAds.Count() - 1;
+ }
+
+ m_nTargetIndex = nIndex;
+
+ // If they click more times while transitioning out, just change the target. If we're
+ // into transitioning in to the next panel, then we need to start the whole thing over.
+ if ( !IsTransitioningOut() )
+ {
+ m_nTransitionStartOffsetX = m_nXPos;
+ float flTransitionTime = 1.f;
+ m_TransitionTimer.Start( flTransitionTime );
+
+ m_ShowTimer.Start( flTransitionTime + m_vecPossibleAds[ m_nCurrentIndex ].m_pAdPanel->GetPresentTime() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCyclingAdContainerPanel::UpdateAdPanelPositions()
+{
+ // Figure out how far along a transition we are
+ float flPercent = Clamp( m_TransitionTimer.GetElapsedTime() / m_TransitionTimer.GetCountdownDuration(), 0.f, 1.f );
+ flPercent = Gain( flPercent, 0.8f );
+
+ // At a certain point, we're no longer transitioning out the old -- we're transitioning in the new
+ const float flTransitionCutOff = m_TransitionTimer.GetCountdownDuration() / 2.f;
+ bool bTransitionOut = flPercent < flTransitionCutOff;
+
+ int nStartX = 0;
+ int nTargetX = 0;
+ float flFadeAmount = 0.f;
+
+ if ( bTransitionOut )
+ {
+ nStartX = m_nTransitionStartOffsetX;
+ nTargetX = m_bTransitionRight ? -100 : 100;
+ flFadeAmount = RemapValClamped( flPercent, 0.f, flTransitionCutOff * 0.75f, 0.f, 255.f );
+ }
+ else
+ {
+ // Once we've passed the middle, show the target
+ m_nCurrentIndex = m_nTargetIndex;
+ nStartX = m_bTransitionRight ? 100 : -100;
+ nTargetX = 0;
+ flFadeAmount = RemapValClamped( flPercent, flTransitionCutOff * 1.25f, 1.f, 255.f, 0.f );
+ }
+
+ // Alpha fades up entirely near the middle to cover the swap
+ m_pFadePanel->SetAlpha( flFadeAmount );
+
+ m_nXPos = RemapVal( flPercent, 0.f, 1.f, nStartX, nTargetX );
+ FOR_EACH_VEC( m_vecPossibleAds, i )
+ {
+ m_vecPossibleAds[i].m_pAdPanel->SetPos( m_nXPos, m_vecPossibleAds[i].m_pAdPanel->GetYPos() );
+ m_vecPossibleAds[i].m_pAdPanel->SetVisible( i == m_nCurrentIndex );
+ }
+}
+
+float CCyclingAdContainerPanel::GetTransitionProgress() const
+{
+ float flPercent = Clamp( m_TransitionTimer.GetElapsedTime() / m_TransitionTimer.GetCountdownDuration(), 0.f, 1.f );
+ return Gain( flPercent, 0.8f );
+}
+
+bool CCyclingAdContainerPanel::IsTransitioningOut() const
+{
+ const float flTransitionCutOff = m_TransitionTimer.GetCountdownDuration() / 2.f;
+ return GetTransitionProgress() < flTransitionCutOff;
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/item_ad_panel.h b/game/client/tf/vgui/item_ad_panel.h
new file mode 100644
index 0000000..795d469
--- /dev/null
+++ b/game/client/tf/vgui/item_ad_panel.h
@@ -0,0 +1,113 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ITEM_AD_PANEL_H
+#define ITEM_AD_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui/VGUI.h>
+#include "vgui_controls/EditablePanel.h"
+
+using namespace vgui;
+class CExButton;
+
+class CBaseAdPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseAdPanel, EditablePanel );
+
+public:
+ CBaseAdPanel( Panel *parent, const char *panelName );
+ virtual ~CBaseAdPanel() {}
+
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+
+ float GetPresentTime() const { return m_flPresentTime; }
+
+ static bool CheckForRequiredSteamComponents( const char* pszSteamRequried, const char* pszOverlayRequired );
+
+private:
+
+ float m_flPresentTime;
+};
+
+class CItemAdPanel : public CBaseAdPanel
+{
+ DECLARE_CLASS_SIMPLE( CItemAdPanel, CBaseAdPanel );
+public:
+ CItemAdPanel( Panel *parent, const char *panelName, item_definition_index_t itemDefIndex );
+ virtual ~CItemAdPanel() {}
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnTick() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+private:
+
+ const CTFItemDefinition* GetItemDef() const;
+ bool m_bShowMarketButton;
+
+ item_definition_index_t m_ItemDefIndex;
+};
+
+class CCyclingAdContainerPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CCyclingAdContainerPanel, EditablePanel );
+public:
+ CCyclingAdContainerPanel( Panel *parent, const char *panelName );
+ virtual ~CCyclingAdContainerPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+
+ void SetItemKVs( KeyValues* pKVItems );
+
+private:
+
+ void CreatePanels();
+ void PresentIndex( int nIndex );
+ void UpdateAdPanelPositions();
+ float GetTransitionProgress() const;
+ bool IsTransitioningOut() const;
+
+ EditablePanel* m_pAdsContainer;
+ EditablePanel* m_pFadePanel;
+ CExButton* m_pPrevButton;
+ CExButton* m_pNextButton;
+ bool m_bNeedsToCreatePanels;
+ bool m_bSettingsApplied;
+
+ struct AdData_t
+ {
+ ~AdData_t()
+ {
+ delete m_pAdPanel;
+ }
+ CBaseAdPanel* m_pAdPanel;
+ KeyValues* m_pSettingsKVs;
+ };
+
+ KeyValues *m_pKVItems;
+
+ CUtlVector< AdData_t > m_vecPossibleAds;
+ int m_nTargetIndex;
+ int m_nCurrentIndex;
+ int m_nTransitionStartOffsetX;
+ bool m_bTransitionRight;
+ int m_nXPos;
+
+ RealTimeCountdownTimer m_TransitionTimer;
+ RealTimeCountdownTimer m_ShowTimer;
+};
+
+#endif // ITEM_AD_PANEL_H
diff --git a/game/client/tf/vgui/item_quickswitch.cpp b/game/client/tf/vgui/item_quickswitch.cpp
new file mode 100644
index 0000000..e7e119f
--- /dev/null
+++ b/game/client/tf/vgui/item_quickswitch.cpp
@@ -0,0 +1,792 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "tf_controls.h"
+#include "tf_gamerules.h"
+#include "tf_shareddefs.h"
+#include "vgui/ISurface.h"
+#include "c_tf_player.h"
+#include "gamestringpool.h"
+#include "iclientmode.h"
+#include "tf_item_inventory.h"
+#include "ienginevgui.h"
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui/IInput.h"
+#include "item_model_panel.h"
+#include "hudelement.h"
+#include "item_quickswitch.h"
+#include "econ_gcmessages.h"
+#include "gc_clientsystem.h"
+#include "loadout_preset_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+#define MAX_QUICKSWITCH_SLOTS 11
+
+extern ConVar tf_respawn_on_loadoutchanges;
+
+extern const char *g_szEquipSlotHeader[CLASS_LOADOUT_POSITION_COUNT];
+int g_SlotsToLoadoutSlotsPerClass[TF_LAST_NORMAL_CLASS][MAX_QUICKSWITCH_SLOTS] =
+{
+ //TF_CLASS_UNDEFINED = 0,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+ // TF_CLASS_SCOUT,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+ // TF_CLASS_SNIPER,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+ // TF_CLASS_SOLDIER,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+ // TF_CLASS_DEMOMAN,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+ // TF_CLASS_MEDIC,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+ // TF_CLASS_HEAVYWEAPONS,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+ // TF_CLASS_PYRO,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+
+#ifdef STAGING_ONLY
+ // TF_CLASS_SPY,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_PDA,
+ LOADOUT_POSITION_PDA2,
+ LOADOUT_POSITION_PDA3,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ },
+#else
+ // TF_CLASS_SPY,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_PDA,
+ LOADOUT_POSITION_PDA2,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+#endif
+
+ // TF_CLASS_ENGINEER,
+ {
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE,
+ LOADOUT_POSITION_PDA,
+ LOADOUT_POSITION_PDA2,
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_ACTION,
+ LOADOUT_POSITION_INVALID,
+ LOADOUT_POSITION_INVALID,
+ },
+};
+
+DECLARE_HUDELEMENT( CItemQuickSwitchPanel );
+
+void IN_QuickSwitchDown( const CCommand &args )
+{
+ // quickswitch disabled in training
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ return;
+ }
+
+ CItemQuickSwitchPanel *pQSPanel = GET_HUDELEMENT( CItemQuickSwitchPanel );
+ if ( pQSPanel )
+ {
+ pQSPanel->OpenQS();
+ }
+}
+
+void IN_QuickSwitchUp( const CCommand &args )
+{
+ // quickswitch disabled in training
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ return;
+ }
+
+ CItemQuickSwitchPanel *pQSPanel = GET_HUDELEMENT( CItemQuickSwitchPanel );
+ if ( pQSPanel )
+ {
+ pQSPanel->CloseQS();
+ }
+}
+
+static ConCommand openquickswitch( "+quickswitch", IN_QuickSwitchDown );
+static ConCommand closequickswitch( "-quickswitch", IN_QuickSwitchUp );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemQuickSwitchPanel::CItemQuickSwitchPanel( const char *pElementName ) : CHudElement( pElementName ), BaseClass( NULL, "ItemQuickSwitchPanel" )
+{
+ Panel *pParent = g_pClientMode->GetViewport();
+ SetParent( pParent );
+
+ SetMouseInputEnabled( true );
+ SetKeyBoardInputEnabled( false );
+
+ m_pItemContainer = vgui::SETUP_PANEL( new vgui::EditablePanel( this, "itemcontainer" ) );
+ m_pItemContainerScroller = vgui::SETUP_PANEL( new vgui::ScrollableEditablePanel( this, m_pItemContainer, "itemcontainerscroller" ) );
+ m_pItemKV = NULL;
+ m_pWeaponLabel = NULL;
+ m_pEquipYourClassLabel = NULL;
+ m_pLoadoutPresetPanel = NULL;
+ m_iClass = TF_CLASS_UNDEFINED;
+ m_iSlot = 0;
+
+ SetVisible( false );
+
+ ListenForGameEvent( "inventory_updated" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemQuickSwitchPanel::~CItemQuickSwitchPanel()
+{
+ if ( m_pItemKV )
+ {
+ m_pItemKV->deleteThis();
+ m_pItemKV = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemQuickSwitchPanel::IsValid( void )
+{
+ return ( m_iClass >= TF_FIRST_NORMAL_CLASS && m_iClass < TF_LAST_NORMAL_CLASS ) &&
+ ( m_iSlot > LOADOUT_POSITION_INVALID && m_iSlot < CLASS_LOADOUT_POSITION_COUNT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CItemQuickSwitchPanel::HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding )
+{
+ if ( !IsVisible() )
+ return 1; // key not handled
+
+ if ( !down )
+ return 1; // key not handled
+
+ int iSlot = 0;
+
+ // convert slot1, slot2 etc to 1,2,3,4
+ if ( pszCurrentBinding && ( !Q_strncmp( pszCurrentBinding, "slot", 4 ) && Q_strlen( pszCurrentBinding ) > 4 ) )
+ {
+ const char *pszNum = pszCurrentBinding + 4;
+ iSlot = atoi( pszNum );
+
+ if ( ( iSlot < 1 ) || ( iSlot >= MAX_QUICKSWITCH_SLOTS ) )
+ {
+ // invalid bind
+ iSlot = 0;
+ }
+ }
+
+ if ( iSlot > 0 )
+ {
+ int iLoadoutSlot = g_SlotsToLoadoutSlotsPerClass[m_iClass][iSlot];
+ if ( iLoadoutSlot != LOADOUT_POSITION_INVALID )
+ {
+ // is it the slot we're already viewing?
+ if ( iLoadoutSlot != m_iSlot )
+ {
+ m_iSlot = iLoadoutSlot;
+
+ if ( IsValid() )
+ {
+ UpdateModelPanels();
+ InvalidateLayout( true );
+ }
+ }
+ }
+ else
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->EmitSound( "Player.DenyWeaponSelection" );
+ }
+ }
+
+ return 0;
+ }
+
+ return 1; // key not handled
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemQuickSwitchPanel::CalculateClassAndSlot( void )
+{
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pPlayer )
+ return false;
+
+ // Get the current class
+ m_iClass = pPlayer->GetPlayerClass()->GetClassIndex();
+ if ( m_iClass < TF_FIRST_NORMAL_CLASS || m_iClass >= TF_LAST_NORMAL_CLASS )
+ return false;
+
+ if ( m_pLoadoutPresetPanel )
+ {
+ m_pLoadoutPresetPanel->SetClass( m_iClass );
+ }
+
+ if ( pPlayer->IsAlive() )
+ {
+ // Get the current weapon slot
+ CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
+ if ( !pWpn )
+ return false;
+
+ m_iSlot = pWpn->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( m_iClass );
+ if ( m_iSlot == LOADOUT_POSITION_INVALID )
+ return false;
+ }
+ else
+ {
+ m_iClass = pPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ m_iSlot = g_SlotsToLoadoutSlotsPerClass[m_iClass][1]; // use the first slot if we're dead
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::OpenQS( void )
+{
+ if ( !CalculateClassAndSlot() )
+ return;
+
+ m_bLoadoutHasChanged = false;
+
+ UpdateModelPanels();
+ SetVisible( true );
+ RequestFocus();
+ MakePopup();
+ SetKeyBoardInputEnabled( false );
+
+ // Force layout now so that we can position the cursor properly
+ InvalidateLayout( true );
+
+ // It takes a few frames before this panel appears the first time, which means
+ // we need to delay until it appears before we can pop the mouse to the right spot.
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ // Move the mouse cursor onto the first entry in the panel that's not our active weapon
+ if ( vgui::surface()->IsCursorVisible() )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+
+ int x,y,w,h;
+ if ( m_pItemPanels.Count() > 1 )
+ {
+ vgui::ipanel()->GetAbsPos( m_pItemPanels[1]->GetVPanel(), x, y );
+ m_pItemPanels[1]->GetSize( w, h );
+ }
+ else
+ {
+ vgui::ipanel()->GetAbsPos( GetVPanel(), x, y );
+ GetSize( w, h );
+ }
+ ::input->SetFullscreenMousePos( x + (w * 0.5), y + (h * 0.5) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::CloseQS( void )
+{
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ SetVisible( false );
+
+ if ( m_bLoadoutHasChanged )
+ {
+ if ( tf_respawn_on_loadoutchanges.GetBool() )
+ {
+ // Tell the GC to tell server that we should respawn if we're in a respawn room
+ GCSDK::CGCMsg< GCSDK::MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange );
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ // Send the preset panel a msg so it can save the change
+ CEconItemView *pCurItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iClass, m_iSlot );
+ if ( pCurItemData )
+ {
+ KeyValues *pLoadoutChangedMsg = new KeyValues( "LoadoutChanged" );
+ pLoadoutChangedMsg->SetInt( "slot", m_iSlot );
+ pLoadoutChangedMsg->SetUint64( "itemid", pCurItemData->GetItemID() );
+ PostMessage( m_pLoadoutPresetPanel, pLoadoutChangedMsg );
+ }
+
+ m_bLoadoutHasChanged = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/ItemQuickSwitch.res" );
+
+ m_pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemSlotLabel") );
+ m_pEquipYourClassLabel = dynamic_cast<vgui::Label*>( FindChildByName("EquipLabel") );
+ m_pNoItemsToEquipLabel = dynamic_cast<vgui::Label*>( FindChildByName("NoItemsLabel") );
+ m_pEquippedLabel = dynamic_cast<CExLabel*>( m_pItemContainer->FindChildByName("CurrentlyEquippedBackground") );
+ m_pLoadoutPresetPanel = dynamic_cast<CLoadoutPresetPanel*>( FindChildByName( "loadout_preset_panel" ) );
+
+ if ( m_pEquippedLabel )
+ {
+ m_pEquippedLabel->SetMouseInputEnabled( false );
+ }
+
+ if ( m_pLoadoutPresetPanel )
+ {
+ m_pLoadoutPresetPanel->EnableVerticalDisplay( true );
+ }
+
+ m_pItemContainerScroller->GetScrollbar()->SetAutohideButtons( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "itemskv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemKV )
+ {
+ m_pItemKV->deleteThis();
+ }
+ m_pItemKV = new KeyValues( "itemkv" );
+ pItemKV->CopySubkeys( m_pItemKV );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ if ( !IsValid() )
+ return;
+
+ // Need to lay these out before we start making item panels inside them
+ m_pItemContainer->InvalidateLayout( true );
+ m_pItemContainerScroller->InvalidateLayout( true );
+
+ // Position the item panels
+ for ( int i = 0; i < m_pItemPanels.Count(); i++ )
+ {
+ if ( m_pItemKV )
+ {
+ m_pItemPanels[i]->ApplySettings( m_pItemKV );
+ m_pItemPanels[i]->InvalidateLayout();
+ }
+
+ int iYDelta = m_pItemPanels[0]->GetTall() + m_iItemPanelYDelta;
+
+ // Once we've setup our first item, we know how large to make the container
+ if ( i == 0 )
+ {
+ m_pItemContainer->SetSize( m_pItemContainer->GetWide(), iYDelta * m_pItemPanels.Count() );
+ }
+
+ // Always indent the top one to make it look better.
+ m_pItemPanels[i]->SetPos( m_iItemPanelXPos, m_iItemPanelYDelta + (iYDelta * i) );
+ }
+
+ // Now that the container has been sized, tell the scroller to re-evaluate
+ m_pItemContainerScroller->InvalidateLayout();
+ m_pItemContainerScroller->GetScrollbar()->InvalidateLayout();
+
+ // Force the class label to layout & resize, so we can align our title
+ if ( m_pEquipYourClassLabel && m_pWeaponLabel )
+ {
+ m_pWeaponLabel->InvalidateLayout( true );
+ m_pWeaponLabel->SizeToContents();
+ int iXPos, iYPos;
+ m_pWeaponLabel->GetPos( iXPos, iYPos );
+ iXPos = ( GetWide() - m_pWeaponLabel->GetWide() ) * 0.5;
+ m_pWeaponLabel->SetPos( iXPos, m_pWeaponLabel->GetTall() );
+ m_pEquipYourClassLabel->SetPos( iXPos, iYPos - m_pEquipYourClassLabel->GetTall() );
+ }
+
+ // If it's visible, put the no items to equip at the bottom
+ if ( m_pNoItemsToEquipLabel->IsVisible() )
+ {
+ int iYDelta = 0;
+ if ( m_pItemPanels.Count() )
+ {
+ iYDelta = m_pItemPanels[0]->GetTall() + m_iItemPanelYDelta;
+ }
+ m_pNoItemsToEquipLabel->SetPos( m_iItemPanelXPos, m_iItemPanelYDelta + (iYDelta * (m_pItemPanels.Count()+1)) );
+ }
+
+ UpdateEquippedItem();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::UpdateEquippedItem( void )
+{
+ if ( !m_pEquippedLabel )
+ return;
+
+ bool bEquipped = false;
+
+ CEconItemView *pCurItemData = TFInventoryManager()->GetItemInLoadoutForClass( m_iClass, m_iSlot );
+ if ( pCurItemData )
+ {
+ if ( pCurItemData->IsValid() )
+ {
+ for ( int i = 0; i < m_pItemPanels.Count(); i++ )
+ {
+ CEconItemView *pItem = m_pItemPanels[i]->GetItem();
+ if ( pItem && ( *pItem == *pCurItemData ) )
+ {
+ int x,y;
+ m_pItemPanels[i]->GetPos( x, y );
+ m_pEquippedLabel->SetPos( x + XRES(3), y + YRES(2) );
+
+ bEquipped = true;
+ }
+ }
+ }
+ else if ( !TFInventoryManager()->SlotContainsBaseItems( GEconItemSchema().GetEquipTypeFromClassIndex( m_iClass ), m_iSlot ) )
+ {
+ for ( int i = 0; i < m_pItemPanels.Count(); i++ )
+ {
+ CEconItemView *pItem = m_pItemPanels[i]->GetItem();
+ if ( !pItem )
+ {
+ int x,y;
+ m_pItemPanels[i]->GetPos( x, y );
+ m_pEquippedLabel->SetPos( x + XRES(3), y + YRES(2) );
+
+ bEquipped = true;
+ }
+ }
+ }
+ }
+
+ if ( m_pEquippedLabel->IsVisible() != bEquipped )
+ {
+ m_pEquippedLabel->SetVisible( bEquipped );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::UpdateModelPanels( void )
+{
+ if ( !IsValid() )
+ return;
+
+ TFPlayerClassData_t *pData = GetPlayerClassData( m_iClass );
+ SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( pData->m_szLocalizableName ) );
+
+ if ( m_pWeaponLabel )
+ {
+ m_pWeaponLabel->SetText( g_szEquipSlotHeader[m_iSlot] );
+ }
+
+ // What items can go in this slot?
+ extern equip_region_mask_t GenerateEquipRegionConflictMask( int iClass, int iUpToSlot, int iIgnoreSlot );
+ const equip_region_mask_t unUsedEquipRegionMask = GenerateEquipRegionConflictMask( m_iClass, m_iSlot, LOADOUT_POSITION_INVALID );
+
+ CEquippableItemsForSlotGenerator equippableItems( m_iClass, m_iSlot, unUsedEquipRegionMask, CEquippableItemsForSlotGenerator::kSlotGenerator_None );
+
+ int iButton = 0;
+ FOR_EACH_VEC( equippableItems.GetDisplayItems(), i )
+ {
+ // For quick-switch, only show items that are equippable and would show up as such in our regular
+ // loadout.
+ if ( equippableItems.GetDisplayItems()[i].m_eDisplayType == CEquippableItemsForSlotGenerator::kSlotDisplay_Normal )
+ {
+ SetButtonToItem( iButton++, equippableItems.GetDisplayItems()[i].m_pEconItemView );
+ }
+ }
+
+ if ( !TFInventoryManager()->SlotContainsBaseItems( GEconItemSchema().GetEquipTypeFromClassIndex( m_iClass ), m_iSlot ) )
+ {
+ SetButtonToItem( iButton++, NULL );
+ }
+
+ if ( m_pNoItemsToEquipLabel )
+ {
+ m_pNoItemsToEquipLabel->SetVisible( iButton == 0 );
+ }
+
+ // Delete excess items
+ for ( int i = m_pItemPanels.Count() - 1; i >= iButton; i-- )
+ {
+ m_pItemPanels[i]->MarkForDeletion();
+ m_pItemPanels.Remove( i );
+ }
+
+ InvalidateLayout();
+
+ // Move the scrollbar to the top
+ m_pItemContainerScroller->GetScrollbar()->SetValue( 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::SetButtonToItem( int iButton, CEconItemView *pItem )
+{
+ CItemModelPanel *pItemPanel;
+ if ( iButton < m_pItemPanels.Count() )
+ {
+ pItemPanel = m_pItemPanels[iButton];
+ }
+ else
+ {
+ const char *pszCommand = VarArgs( "itempanel%d", iButton );
+ pItemPanel = new CItemModelPanel( m_pItemContainer, pszCommand );
+ if ( m_pItemKV )
+ {
+ pItemPanel->ApplySettings( m_pItemKV );
+ }
+ pItemPanel->MakeReadyForUse();
+ pItemPanel->SetActAsButton( true, true );
+ pItemPanel->SendPanelEnterExits( true );
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ pItemPanel->SetBorder( pScheme->GetBorder( "EconItemBorder" ) );
+
+ m_pItemPanels.AddToTail( pItemPanel );
+ }
+
+ pItemPanel->SetNoItemText( "#SelectNoItemSlot" );
+ pItemPanel->SetItem( pItem );
+ pItemPanel->AddActionSignalTarget( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ if ( pItemPanel )
+ {
+ pItemPanel->SetPaintBorderEnabled( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::OnItemPanelExited( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ if ( pItemPanel )
+ {
+ pItemPanel->SetPaintBorderEnabled( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::OnIPMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ if ( !pItemPanel )
+ return;
+
+ itemid_t iIndex = INVALID_ITEM_ID;
+
+ CEconItemView *pItemData = pItemPanel->GetItem();
+ if ( pItemData && pItemData->IsValid() )
+ {
+ iIndex = pItemData->GetItemID();
+ }
+
+ TFInventoryManager()->EquipItemInLoadout( m_iClass, m_iSlot, iIndex );
+
+ m_bLoadoutHasChanged = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( !IsVisible() )
+ return;
+
+ const char * type = event->GetName();
+
+ if ( Q_strcmp( type, "inventory_updated" ) == 0 )
+ {
+ UpdateEquippedItem();
+ }
+ else
+ {
+ CHudElement::FireGameEvent( event );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemQuickSwitchPanel::OnItemPresetLoaded()
+{
+ m_bLoadoutHasChanged = true;
+}
diff --git a/game/client/tf/vgui/item_quickswitch.h b/game/client/tf/vgui/item_quickswitch.h
new file mode 100644
index 0000000..5cf8bd8
--- /dev/null
+++ b/game/client/tf/vgui/item_quickswitch.h
@@ -0,0 +1,72 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#ifndef ITEM_QUICKSWITCH_H
+#define ITEM_QUICKSWITCH_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/ScrollableEditablePanel.h"
+
+class CLoadoutPresetPanel;
+
+class CItemQuickSwitchPanel : public vgui::EditablePanel, public CHudElement
+{
+ DECLARE_CLASS_SIMPLE( CItemQuickSwitchPanel, vgui::EditablePanel );
+public:
+ CItemQuickSwitchPanel( const char *pElementName );
+ virtual ~CItemQuickSwitchPanel();
+
+ void OpenQS( void );
+ void CloseQS( void );
+ bool ShouldDraw( void ) { return IsVisible(); }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void PerformLayout( void );
+ virtual void OnTick( void );
+
+ void UpdateEquippedItem( void );
+ bool CalculateClassAndSlot();
+ void UpdateModelPanels( void );
+ void SetButtonToItem( int iButton, CEconItemView *pItem );
+
+ bool IsValid( void );
+
+ virtual void FireGameEvent( IGameEvent *event );
+
+ int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding );
+
+ MESSAGE_FUNC( OnItemPresetLoaded, "ItemPresetLoaded" );
+
+ MESSAGE_FUNC_PTR( OnIPMouseReleased, "ItemPanelMouseReleased", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel );
+
+private:
+ int m_iClass; // Class of the player we're selecting an item for
+ int m_iSlot; // Slot on the player that we're selecting an item for
+ bool m_bLoadoutHasChanged;
+
+ vgui::EditablePanel *m_pItemContainer;
+ vgui::ScrollableEditablePanel *m_pItemContainerScroller;
+ vgui::Label *m_pWeaponLabel;
+ vgui::Label *m_pEquipYourClassLabel;
+ vgui::Label *m_pNoItemsToEquipLabel;
+ vgui::Label *m_pEquippedLabel;
+
+ CLoadoutPresetPanel *m_pLoadoutPresetPanel;
+
+ KeyValues *m_pItemKV;
+ CUtlVector<CItemModelPanel *> m_pItemPanels;
+
+ CPanelAnimationVarAliasType( int, m_iItemPanelXPos, "itempanel_xpos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemPanelYDelta, "itempanel_ydelta", "0", "proportional_int" );
+};
+
+#endif // ITEM_QUICKSWITCH_H
diff --git a/game/client/tf/vgui/item_slot_panel.cpp b/game/client/tf/vgui/item_slot_panel.cpp
new file mode 100644
index 0000000..d09852f
--- /dev/null
+++ b/game/client/tf/vgui/item_slot_panel.cpp
@@ -0,0 +1,301 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "item_slot_panel.h"
+#include "tf_item_inventory.h"
+#include "item_selection_panel.h"
+#include "tf_gcmessages.h"
+#include "gc_clientsystem.h"
+
+#define NUM_MAX_SLOTS 1
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemSlotPanel::CItemSlotPanel( vgui::Panel *parent )
+ : CBaseLoadoutPanel( parent, "item_slot_panel" )
+{
+ m_pItem = NULL;
+ m_pSelectionPanel = NULL;
+ m_iCurrentSlotIndex = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemSlotPanel::~CItemSlotPanel()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/ItemSlotPanel.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( !m_itemSlots[i].m_bHasSlot )
+ {
+ m_pItemModelPanels[i]->SetVisible( false );
+ continue;
+ }
+
+ int iCenter = GetWide() * 0.5;
+ int iButtonX = (i % GetNumColumns());
+ int iButtonY = (i / GetNumColumns());
+ int iXPos = (iCenter + m_iItemBackpackOffcenterX) + (iButtonX * m_pItemModelPanels[i]->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ int iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[i]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+
+ m_pItemModelPanels[i]->SetPos( iXPos, iYPos );
+ m_pItemModelPanels[i]->SetVisible( true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i] == pItemPanel )
+ {
+ OnCommand( VarArgs("change%d", i) );
+ return;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::OnSelectionReturned( KeyValues *data )
+{
+ if ( data )
+ {
+ uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );
+ if ( ulIndex != INVALID_ITEM_ID )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( ulIndex );
+ if ( pItemData )
+ {
+ m_pItemModelPanels[ m_iCurrentSlotIndex ]->SetItem( pItemData );
+
+ itemid_t ulOriginalID = pItemData->GetSOCData()->GetOriginalID();
+
+ m_itemSlots[ m_iCurrentSlotIndex ].m_ulOriginalID = ulOriginalID;
+
+ // tell GC to update the slot attribute
+ GCSDK::CProtoBufMsg<CMsgSetItemSlotAttribute> msg( k_EMsgGC_ClientSetItemSlotAttribute );
+
+ msg.Body().set_item_id( m_pItem->GetItemID() );
+ msg.Body().set_slot_item_original_id( ulOriginalID );
+ msg.Body().set_slot_index( m_iCurrentSlotIndex + 1 );
+
+ //EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_upgrade_card", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+ }
+ }
+
+ PostMessage( GetParent(), new KeyValues("SelectionEnded") );
+
+ // It'll have deleted itself, so we don't need to clean it up
+ m_pSelectionPanel = NULL;
+ OnCancelSelection();
+
+ // find the selected item and give it the focus
+ CItemModelPanel *pSelection = GetFirstSelectedItemModelPanel( true );
+ if( !pSelection )
+ {
+ m_pItemModelPanels[0]->SetSelected( true );
+ pSelection = m_pItemModelPanels[0];
+ }
+
+ pSelection->RequestFocus();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::OnCancelSelection( void )
+{
+ if ( m_pSelectionPanel )
+ {
+ m_pSelectionPanel->SetVisible( false );
+ m_pSelectionPanel->MarkForDeletion();
+ m_pSelectionPanel = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::OnCommand( const char *command )
+{
+ if ( !V_stricmp( command, "ok" ) )
+ {
+ SetVisible( false );
+ return;
+ }
+ else if ( V_stristr( command, "change" ) )
+ {
+ const char *pszNum = command+6;
+ if ( pszNum && pszNum[0] )
+ {
+ int iSlot = atoi(pszNum);
+ if ( iSlot >= 0 && iSlot < m_itemSlots.Count() )
+ {
+ if ( m_iCurrentSlotIndex != iSlot )
+ {
+ m_iCurrentSlotIndex = iSlot;
+ }
+
+ m_selectionCriteria = CItemSelectionCriteria();
+ m_selectionCriteria.SetTags( m_itemSlots[m_iCurrentSlotIndex].m_slotCriteriaAttribute.tags().c_str() );
+ m_selectionCriteria.SetIgnoreEnabledFlag( true );
+
+ // Create the selection screen. It removes itself on close.
+ m_pSelectionPanel = new CItemCriteriaSelectionPanel( this, &m_selectionCriteria );
+ m_pSelectionPanel->InvalidateLayout( false, true ); // need to ApplySchemeSettings now so it doesn't override our SetDialogVariable below later
+ m_pSelectionPanel->ShowPanel( 0, true );
+ m_pSelectionPanel->SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( "#EditSlots_SelectItemPanel" ) );
+
+ PostMessage( GetParent(), new KeyValues("SelectionStarted") );
+ }
+ }
+
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::UpdateModelPanels( void )
+{
+ // For now, fill them out with the local player's currently wielded items
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByOriginalID( m_itemSlots[i].m_ulOriginalID );
+ m_pItemModelPanels[i]->SetItem( pItemData );
+ m_pItemModelPanels[i]->SetShowQuantity( true );
+ m_pItemModelPanels[i]->SetSelected( false );
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+
+ // Now layout again to position our item buttons
+ InvalidateLayout();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CItemSlotPanel::GetNumItemPanels( void )
+{
+ return NUM_MAX_SLOTS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
+{
+ if ( bVisible )
+ {
+ if ( m_pSelectionPanel )
+ {
+ m_pSelectionPanel->SetVisible( false );
+ m_pSelectionPanel->MarkForDeletion();
+ m_pSelectionPanel = NULL;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::AddNewItemPanel( int iPanelIndex )
+{
+ BaseClass::AddNewItemPanel( iPanelIndex );
+
+ m_itemSlots.AddToTail();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSlotPanel::SetItem( CEconItem* pItem )
+{
+ if ( !pItem )
+ {
+ SetVisible( false );
+ return;
+ }
+
+ OnCancelSelection();
+
+ m_pItem = pItem;
+
+ static CSchemaAttributeDefHandle s_itemSlotCriteriaAttributes[] =
+ {
+ CSchemaAttributeDefHandle( "item slot criteria 1" ),
+ };
+ COMPILE_TIME_ASSERT( ARRAYSIZE( s_itemSlotCriteriaAttributes ) == NUM_MAX_SLOTS );
+
+ static CSchemaAttributeDefHandle s_itemInSlotAttributes[] =
+ {
+ CSchemaAttributeDefHandle( "item in slot 1" ),
+ };
+ COMPILE_TIME_ASSERT( ARRAYSIZE( s_itemInSlotAttributes ) == NUM_MAX_SLOTS );
+
+ for ( int i=0; i<ARRAYSIZE( s_itemSlotCriteriaAttributes ); ++i )
+ {
+ m_itemSlots[i].m_bHasSlot = false;
+ if ( m_pItem->FindAttribute( s_itemSlotCriteriaAttributes[i], &m_itemSlots[i].m_slotCriteriaAttribute ) )
+ {
+ m_itemSlots[i].m_bHasSlot = true;
+ m_itemSlots[i].m_ulOriginalID = INVALID_ITEM_ID;
+ m_pItem->FindAttribute( s_itemInSlotAttributes[i], &m_itemSlots[i].m_ulOriginalID );
+ }
+ }
+
+ UpdateModelPanels();
+}
diff --git a/game/client/tf/vgui/item_slot_panel.h b/game/client/tf/vgui/item_slot_panel.h
new file mode 100644
index 0000000..357ce87
--- /dev/null
+++ b/game/client/tf/vgui/item_slot_panel.h
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#ifndef ITEM_SLOT_PANEL_H
+#define ITEM_SLOT_PANEL_H
+#include "base_loadout_panel.h"
+
+class CItemCriteriaSelectionPanel;
+
+//-----------------------------------------------------------------------------
+// A loadout screen that handles modifying the loadout of a specific item
+//-----------------------------------------------------------------------------
+class CItemSlotPanel : public CBaseLoadoutPanel
+{
+ DECLARE_CLASS_SIMPLE( CItemSlotPanel, CBaseLoadoutPanel );
+public:
+ CItemSlotPanel( vgui::Panel *parent );
+ ~CItemSlotPanel();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+
+ virtual void AddNewItemPanel( int iPanelIndex ) OVERRIDE;
+ virtual void UpdateModelPanels( void ) OVERRIDE;
+ virtual int GetNumItemPanels( void ) OVERRIDE;
+ virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory ) OVERRIDE;
+
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel );
+ MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data );
+ MESSAGE_FUNC( OnCancelSelection, "CancelSelection" );
+ virtual void OnCommand( const char *command );
+
+ void SetItem( CEconItem* pItem );
+
+private:
+ CEconItem *m_pItem;
+
+ struct ItemSlot_t
+ {
+ CAttribute_ItemSlotCriteria m_slotCriteriaAttribute;
+ itemid_t m_ulOriginalID;
+ bool m_bHasSlot;
+ };
+ CUtlVector< ItemSlot_t > m_itemSlots;
+
+ int m_iCurrentSlotIndex;
+ CItemSelectionCriteria m_selectionCriteria;
+ CItemCriteriaSelectionPanel *m_pSelectionPanel;
+};
+
+#endif // ITEM_SLOT_PANEL_H
diff --git a/game/client/tf/vgui/loadout_preset_panel.cpp b/game/client/tf/vgui/loadout_preset_panel.cpp
new file mode 100644
index 0000000..b9998ce
--- /dev/null
+++ b/game/client/tf/vgui/loadout_preset_panel.cpp
@@ -0,0 +1,250 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cbase.h"
+#include "loadout_preset_panel.h"
+#include "tf_item_inventory.h"
+#include "econ/econ_item_preset.h"
+#include "econ/econ_item_system.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+
+DECLARE_BUILD_FACTORY( CLoadoutPresetPanel );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CLoadoutPresetPanel::CLoadoutPresetPanel( vgui::Panel *pParent, const char *pName )
+: EditablePanel( pParent, "loadout_preset_panel" )
+{
+ V_memset( m_pPresetButtons, 0, sizeof( m_pPresetButtons ) );
+
+ m_iClass = TF_CLASS_UNDEFINED;
+ m_pPresetButtonKv = NULL;
+ m_bDisplayVertical = false;
+
+ // Create all buttons
+ for ( int i = 0; i < MAX_PRESETS; ++i )
+ {
+ CFmtStr fmtTokenName( "TF_ItemPresetName%i", i );
+ CFmtStr fmtButtonName( "LoadPresetButton%i", i );
+ wchar_t *pwszPresetName = g_pVGuiLocalize->Find( fmtTokenName.Access() );
+ m_pPresetButtons[i] = new CExButton( this, fmtButtonName.Access(), pwszPresetName, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/LoadoutPresetPanel.res" );
+
+ m_aDefaultColors[LOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorFg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorFg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorFg", Color( 255, 255, 255, 255 ) );
+
+ m_aDefaultColors[LOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDefaultColorBg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetArmedColorBg", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[LOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Econ.Button.PresetDepressedColorBg", Color( 255, 255, 255, 255 ) );
+
+ m_aDefaultColors[NOTLOADED][FG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.TextColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][FG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedTextColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][FG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedTextColor", Color( 255, 255, 255, 255 ) );
+
+ m_aDefaultColors[NOTLOADED][BG][DEFAULT] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.BgColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][BG][ARMED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.ArmedBgColor", Color( 255, 255, 255, 255 ) );
+ m_aDefaultColors[NOTLOADED][BG][DEPRESSED] = vgui::scheme()->GetIScheme( GetScheme() )->GetColor( "Button.DepressedBgColor", Color( 255, 255, 255, 255 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::ApplySettings( KeyValues *pInResourceData )
+{
+ BaseClass::ApplySettings( pInResourceData );
+
+ KeyValues *pPresetButtonKv = pInResourceData->FindKey( "presetbutton_kv" );
+ if ( pPresetButtonKv && !m_pPresetButtonKv )
+ {
+ m_pPresetButtonKv = new KeyValues( "presetbutton_kv" );
+ pPresetButtonKv->CopySubkeys( m_pPresetButtonKv );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( !m_pPresetButtons[0] )
+ return;
+
+ const int nBuffer = XRES( 2 );
+
+ for ( int i = 0; i < MAX_PRESETS; ++i )
+ {
+ if ( m_pPresetButtonKv )
+ {
+ m_pPresetButtons[i]->ApplySettings( m_pPresetButtonKv );
+ }
+
+ // Display buttons vertically or horizontally?
+ // NOTE: Button width and height will be valid here, since we've just applied settings
+ if ( m_bDisplayVertical )
+ {
+ const int nButtonHeight = m_pPresetButtons[0]->GetTall();
+ m_pPresetButtons[i]->SetPos( 0, i * ( nButtonHeight + nBuffer ) );
+ }
+ else
+ {
+ const int nButtonWidth = m_pPresetButtons[0]->GetWide();
+ const int nStartX = 0.5f * ( GetWide() - MAX_PRESETS * ( nButtonWidth + nBuffer ) );
+ m_pPresetButtons[i]->SetPos( nStartX + i * ( nButtonWidth + nBuffer ), 0 );
+ }
+ m_pPresetButtons[i]->SetVisible( true );
+ }
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 150 );
+
+ UpdatePresetButtonStates();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::SetClass( int iClass )
+{
+ m_iClass = iClass;
+
+ if ( iClass != TF_CLASS_UNDEFINED )
+ {
+ UpdatePresetButtonStates();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::EnableVerticalDisplay( bool bVertical )
+{
+ m_bDisplayVertical = bVertical;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::LoadPreset( int iPresetIndex )
+{
+ TFInventoryManager()->LoadPreset( m_iClass, iPresetIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::OnCommand( const char *command )
+{
+ if ( !V_strnicmp( command, "loadpreset_", 11 ) )
+ {
+ const int iPresetIndex = atoi( command + 11 );
+ LoadPreset( iPresetIndex );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::OnTick()
+{
+ UpdatePresetButtonStates();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Processes some keypresses for the loadout panel
+//-----------------------------------------------------------------------------
+bool CLoadoutPresetPanel::HandlePresetKeyPressed( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if( nButtonCode == KEY_XBUTTON_LEFT_SHOULDER )
+ {
+ if( GetSelectedPresetID() > 0 )
+ LoadPreset( GetSelectedPresetID() - 1 );
+ return true;
+ }
+ else if( nButtonCode == KEY_XBUTTON_RIGHT_SHOULDER )
+ {
+ if( GetSelectedPresetID() < MAX_PRESETS - 1 )
+ LoadPreset( GetSelectedPresetID() + 1 );
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+equipped_preset_t CLoadoutPresetPanel::GetSelectedPresetID() const
+{
+ if ( !InventoryManager()->GetLocalInventory() )
+ return INVALID_PRESET_INDEX;
+
+ const uint32 unAccountID = InventoryManager()->GetLocalInventory()->GetOwner().GetAccountID();
+ const CEconItemPerClassPresetData soSearch( unAccountID, m_iClass );
+
+ GCSDK::CSharedObjectCache *pSOCache = InventoryManager()->GetLocalInventory()->GetSOC();
+ if ( !pSOCache )
+ return INVALID_PRESET_INDEX;
+
+ const CEconItemPerClassPresetData *pExistingPerClassData = assert_cast<CEconItemPerClassPresetData *>( pSOCache->FindSharedObject( soSearch ) );
+ if ( !pExistingPerClassData )
+ return INVALID_PRESET_INDEX;
+
+ return pExistingPerClassData->GetActivePreset();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLoadoutPresetPanel::UpdatePresetButtonStates()
+{
+ const equipped_preset_t unEquippedPresetID = GetSelectedPresetID();
+
+ for ( int i = 0; i < MAX_PRESETS; ++i )
+ {
+ if ( i == unEquippedPresetID )
+ {
+ m_pPresetButtons[i]->SetDefaultColor( m_aDefaultColors[LOADED][FG][DEFAULT], m_aDefaultColors[LOADED][BG][DEFAULT] );
+ m_pPresetButtons[i]->SetArmedColor( m_aDefaultColors[LOADED][FG][ARMED], m_aDefaultColors[LOADED][BG][ARMED] );
+ m_pPresetButtons[i]->SetDepressedColor( m_aDefaultColors[LOADED][FG][DEPRESSED], m_aDefaultColors[LOADED][BG][DEPRESSED] );
+ }
+ else
+ {
+ m_pPresetButtons[i]->SetDefaultColor( m_aDefaultColors[NOTLOADED][FG][DEFAULT], m_aDefaultColors[NOTLOADED][BG][DEFAULT] );
+ m_pPresetButtons[i]->SetArmedColor( m_aDefaultColors[NOTLOADED][FG][ARMED], m_aDefaultColors[NOTLOADED][BG][ARMED] );
+ m_pPresetButtons[i]->SetDepressedColor( m_aDefaultColors[NOTLOADED][FG][DEPRESSED], m_aDefaultColors[NOTLOADED][BG][DEPRESSED] );
+ }
+
+ CFmtStr fmtCmd( "loadpreset_%i", i );
+ m_pPresetButtons[i]->SetCommand( fmtCmd.Access() );
+ }
+}
diff --git a/game/client/tf/vgui/loadout_preset_panel.h b/game/client/tf/vgui/loadout_preset_panel.h
new file mode 100644
index 0000000..7004832
--- /dev/null
+++ b/game/client/tf/vgui/loadout_preset_panel.h
@@ -0,0 +1,64 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef LOADOUT_PRESET_PANEL_H
+#define LOADOUT_PRESET_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/PHandle.h"
+#include "class_loadout_panel.h"
+
+class CExButton;
+class CSelectedItemPreset;
+
+//-----------------------------------------------------------------------------
+// A loadout preset panel, which allows combinations of items to be saved and
+// restored via the GC.
+//-----------------------------------------------------------------------------
+class CLoadoutPresetPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CLoadoutPresetPanel, vgui::EditablePanel );
+public:
+ CLoadoutPresetPanel( vgui::Panel *pParent, const char *pName ); // name is ignored but needed for DECLARE_BUILD_FACTORY()
+
+ void SetClass( int iClass );
+ void EnableVerticalDisplay( bool bVertical );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *pInResourceData );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *command );
+ virtual void OnTick() OVERRIDE;
+
+ bool HandlePresetKeyPressed( vgui::KeyCode code );
+
+private:
+ equipped_preset_t GetSelectedPresetID() const;
+ void UpdatePresetButtonStates();
+ void LoadPreset( int iPresetIndex );
+
+ enum PresetsConsts_t
+ {
+ MAX_PRESETS = 4,
+ };
+
+ int m_iClass;
+ KeyValues *m_pPresetButtonKv;
+ CExButton *m_pPresetButtons[ MAX_PRESETS ];
+ bool m_bDisplayVertical;
+
+ enum PresetButtonColors_t
+ {
+ LOADED = 0, NOTLOADED,
+ FG = 0, BG,
+ DEFAULT = 0, ARMED, DEPRESSED
+ };
+ Color m_aDefaultColors[2][2][3]; // [LOADED|NOTLOADED][FG|BG][DEFAULT|ARMED|DEPRESSED]
+};
+
+
+#endif // LOADOUT_PRESET_PANEL_H
diff --git a/game/client/tf/vgui/modelimagepanel.cpp b/game/client/tf/vgui/modelimagepanel.cpp
new file mode 100644
index 0000000..e322282
--- /dev/null
+++ b/game/client/tf/vgui/modelimagepanel.cpp
@@ -0,0 +1,214 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#include "cbase.h"
+#include "modelimagepanel.h"
+#include "iconrenderreceiver.h"
+#include "materialsystem/imaterialvar.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "renderparm.h"
+
+
+using namespace vgui;
+
+const char *g_pszModelImagePanelRTName = "_rt_ModelImagePanel";
+static vgui::DHANDLE<CModelImagePanel> s_hModelImageLockPanel;
+
+#ifdef STAGING_ONLY
+ConVar tf_modelimagepanel_ignore_cache( "tf_modelimagepanel_ignore_cache", "0" );
+#endif
+
+DECLARE_BUILD_FACTORY( CModelImagePanel );
+
+CModelImagePanel::CModelImagePanel( vgui::Panel *pParent, const char *pName )
+ : BaseClass( pParent, pName )
+{
+ m_pCachedIcon = NULL;
+ m_pCachedMaterial = NULL;
+ m_iCachedTextureID = -1;
+}
+
+CModelImagePanel::~CModelImagePanel()
+{
+ InvalidateImage();
+}
+
+void CModelImagePanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ InvalidateImage();
+}
+
+void CModelImagePanel::OnSizeChanged( int wide, int tall )
+{
+ BaseClass::OnSizeChanged( wide, tall );
+
+ InvalidateImage();
+}
+
+void CModelImagePanel::Paint()
+{
+ // don't do anything for invalid model
+ if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
+ {
+ return;
+ }
+
+ // check lock panel
+ if ( s_hModelImageLockPanel )
+ {
+ // waiting for async copy to finish
+ if ( s_hModelImageLockPanel->m_pCachedIcon && s_hModelImageLockPanel->m_pCachedIcon->GetTexture() )
+ {
+ s_hModelImageLockPanel = NULL;
+ }
+ }
+
+ if ( m_pCachedIcon )
+ {
+ if ( m_pCachedIcon->GetTexture() )
+ {
+ if ( !m_pCachedMaterial && g_pMaterialSystem )
+ {
+ const char *pszTextureName = m_pCachedIcon->GetTexture()->GetName();
+ KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
+ pVMTKeyValues->SetString( "$basetexture", pszTextureName );
+ pVMTKeyValues->SetInt( "$translucent", 1 );
+ pVMTKeyValues->SetInt( "$vertexcolor", 1 );
+ IMaterial *pMaterial = g_pMaterialSystem->FindProceduralMaterial( pszTextureName, TEXTURE_GROUP_VGUI, pVMTKeyValues );
+ SafeAssign( &m_pCachedMaterial, pMaterial );
+
+ bool bFound = false;
+ IMaterialVar *pVar = m_pCachedMaterial->FindVar( "$basetexture", &bFound );
+ if ( bFound && pVar )
+ {
+ pVar->SetTextureValue( m_pCachedIcon->GetTexture() );
+ m_pCachedMaterial->RefreshPreservingMaterialVars();
+ }
+ }
+
+ if ( m_iCachedTextureID == -1 )
+ {
+ m_iCachedTextureID = g_pMatSystemSurface->DrawGetTextureId( m_pCachedIcon->GetTexture() );
+ g_pMatSystemSurface->DrawSetTextureMaterial( m_iCachedTextureID, m_pCachedMaterial );
+ }
+ }
+ else
+ {
+ // still waiting for texture
+ BaseClass::Paint();
+ return;
+ }
+ }
+
+ // just draw the texture if we got one.
+ if ( m_iCachedTextureID != -1 )
+ {
+ surface()->DrawSetTexture( m_iCachedTextureID );
+ surface()->DrawSetColor( 255, 255, 255, 255 );
+ const int iWidth = GetWide();
+ const int iHeight = GetTall();
+ const int iMappingWitdh = m_pCachedMaterial->GetMappingWidth();
+ const int iMappingHeight = m_pCachedMaterial->GetMappingHeight();
+ float flTexW, flTexH;
+ if ( iWidth > iMappingWitdh || iHeight > iMappingHeight )
+ {
+ float flScale = iWidth > iHeight ? (float)iMappingWitdh / iWidth : (float)iMappingHeight / iHeight;
+ flTexW = ( flScale * iWidth ) / iMappingWitdh;
+ flTexH = ( flScale * iHeight ) / iMappingHeight;
+ }
+ else
+ {
+ flTexW = (float)( iWidth - 1 ) / iMappingWitdh;
+ flTexH = (float)( iHeight - 1 ) / iMappingHeight;
+ }
+ surface()->DrawTexturedSubRect( 0, 0, iWidth, iHeight, 0.f, 0.f, flTexW, flTexH );
+ return;
+ }
+
+ // can't find available cache render target, don't do anything
+ if ( s_hModelImageLockPanel != NULL && s_hModelImageLockPanel != this )
+ {
+ BaseClass::Paint();
+ return;
+ }
+
+ CMatRenderContextPtr pRenderContext( materials );
+
+ // Turn off depth-write to dest alpha so that we get white there instead. The code that uses
+ // the render target needs a mask of where stuff was rendered.
+ pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false );
+
+ g_pMatSystemSurface->Set3DPaintTempRenderTarget( g_pszModelImagePanelRTName );
+
+ BaseClass::Paint();
+
+ // copy the rendered weapon skin from the render target
+ Assert( m_pCachedIcon == NULL );
+ CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_RootMDL.m_MDL.GetMDL() ), g_pMDLCache );
+ char buffer[_MAX_PATH];
+ CUtlString strMDLName = V_GetFileName( studioHdr.pszName() );
+ V_sprintf_safe( buffer, "proc/icon/mdl_%s_body%d_skin%d_w%d_h%d", strMDLName.StripExtension().Get(), m_RootMDL.m_MDL.m_nBody, m_RootMDL.m_MDL.m_nSkin, GetWide(), GetTall() );
+ SafeAssign( &m_pCachedIcon, new CIconRenderReceiver() );
+
+ // If the icon still exists in the material system, don't bother regenerating it.
+ if ( materials->IsTextureLoaded( buffer )
+#ifdef STAGING_ONLY
+ && !tf_modelimagepanel_ignore_cache.GetBool()
+#endif
+ )
+ {
+ ITexture* resTexture = materials->FindTexture( buffer, TEXTURE_GROUP_RUNTIME_COMPOSITE, false, 0 );
+ if ( resTexture && resTexture->IsError() == false )
+ {
+ m_pCachedIcon->OnAsyncCreateComplete( resTexture, NULL );
+ }
+ }
+ else
+ {
+ // No icon available yet, need to create it.
+ ITexture *pRenderTarget = g_pMaterialSystem->FindTexture( g_pszModelImagePanelRTName, TEXTURE_GROUP_RENDER_TARGET );
+ if ( pRenderTarget )
+ {
+ pRenderContext->AsyncCreateTextureFromRenderTarget( pRenderTarget, buffer, IMAGE_FORMAT_RGBA8888, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP, m_pCachedIcon, NULL );
+
+ // make this panel lock the render target
+ s_hModelImageLockPanel = this;
+ }
+ }
+
+ g_pMatSystemSurface->Reset3DPaintTempRenderTarget();
+}
+
+void CModelImagePanel::SetMDL( MDLHandle_t handle, void *pProxyData /*= NULL*/ )
+{
+ BaseClass::SetMDL( handle, pProxyData );
+ InvalidateImage();
+}
+
+void CModelImagePanel::SetMDL( const char *pMDLName, void *pProxyData /*= NULL*/ )
+{
+ BaseClass::SetMDL( pMDLName, pProxyData );
+}
+
+void CModelImagePanel::SetMDLBody( unsigned int nBody )
+{
+ SetBody( nBody );
+ InvalidateImage();
+}
+
+void CModelImagePanel::SetMDLSkin( int nSkin )
+{
+ SetSkin( nSkin );
+ InvalidateImage();
+}
+
+void CModelImagePanel::InvalidateImage()
+{
+ SafeRelease( &m_pCachedIcon );
+ SafeRelease( &m_pCachedMaterial );
+ m_iCachedTextureID = -1;
+}
diff --git a/game/client/tf/vgui/modelimagepanel.h b/game/client/tf/vgui/modelimagepanel.h
new file mode 100644
index 0000000..4c2ede0
--- /dev/null
+++ b/game/client/tf/vgui/modelimagepanel.h
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#ifndef MODELIMAGEPANEL_H
+#define MODELIMAGEPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "basemodel_panel.h"
+
+class CIconRenderReceiver;
+
+class CModelImagePanel : public CBaseModelPanel
+{
+DECLARE_CLASS_SIMPLE( CModelImagePanel, CBaseModelPanel );
+
+public:
+
+ // Constructor, Destructor.
+ CModelImagePanel( vgui::Panel *pParent, const char *pName );
+ virtual ~CModelImagePanel();
+
+ virtual void PerformLayout() OVERRIDE;
+ virtual void Paint() OVERRIDE;
+ virtual void OnSizeChanged( int wide, int tall ) OVERRIDE;
+
+ virtual void SetMDL( MDLHandle_t handle, void *pProxyData = NULL ) OVERRIDE;
+ virtual void SetMDL( const char *pMDLName, void *pProxyData = NULL ) OVERRIDE;
+ void SetMDLBody( unsigned int nBody );
+ void SetMDLSkin( int nSkin );
+
+ void InvalidateImage();
+
+private:
+ CIconRenderReceiver *m_pCachedIcon;
+ IMaterial *m_pCachedMaterial;
+ int m_iCachedTextureID;
+};
+
+#endif // MODELIMAGEPANEL_H
diff --git a/game/client/tf/vgui/quest_item_panel.cpp b/game/client/tf/vgui/quest_item_panel.cpp
new file mode 100644
index 0000000..cfa05fe
--- /dev/null
+++ b/game/client/tf/vgui/quest_item_panel.cpp
@@ -0,0 +1,1625 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "quest_item_panel.h"
+#include "tf_hud_item_progress_tracker.h"
+#include "quest_log_panel.h"
+#include "c_tf_player.h"
+#include "econ_item_description.h"
+#include "clientmode_tf.h"
+#include <vgui_controls/AnimationController.h>
+#include "quest_objective_manager.h"
+#include "econ_quests.h"
+#include "confirm_dialog.h"
+#include "tf_quest_restriction.h"
+#include "item_model_panel.h"
+#include "tf_gc_client.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
+
+const float k_flQuestDecodeTime = 2.f;
+const float k_flQuestTurnInTime = 5.f;
+ConVar tf_quest_turn_in_confirm_opt_out( "tf_quest_turn_in_confirm_opt_out", "0", FCVAR_ARCHIVE, "If nonzero, don't confirm submitting a contract that does not have all of the bonus points" );
+
+extern CQuestLogPanel *GetQuestLog();
+extern CQuestTooltip* g_spTextTooltip;
+
+extern const char *s_pszMMTypes[kMatchmakingTypeCount];
+extern const char *s_pszGameModes[eNumGameCategories];
+
+void SelectGroup( EMatchmakingGroupType eGroup, bool bSelected );
+void SelectCategory( EGameCategory eCategory, bool bSelected );
+
+void PromptOrFireCommand( const char* pszCommand );
+
+static void ConfirmDiscardQuest( bool bConfirmed, void* pContext )
+{
+ CQuestItemPanel *pQuestItemPanel = ( CQuestItemPanel* )pContext;
+ if ( pQuestItemPanel )
+ {
+ pQuestItemPanel->OnConfirmDelete( bConfirmed );
+ }
+}
+
+static void ConfirmEquipLoaners( bool bConfirmed, void* pContext )
+{
+ CQuestItemPanel *pQuestItemPanel = ( CQuestItemPanel* )pContext;
+ if ( pQuestItemPanel )
+ {
+ pQuestItemPanel->OnConfirmEquipLoaners( bConfirmed );
+ }
+}
+
+static void ConfirmTurnInQuest( bool bConfirmed, void* pContext )
+{
+ if ( bConfirmed )
+ {
+ CQuestItemPanel *pQuestItemPanel = (CQuestItemPanel*)pContext;
+ if ( pQuestItemPanel )
+ {
+ pQuestItemPanel->OnCompleteQuest();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: fill vecLoanerItems with loaners def indices from pQuest
+//-----------------------------------------------------------------------------
+static int GetLoanerListFromQuest( const CEconItemView *pQuest, CUtlVector< item_definition_index_t >& vecLoanerItems )
+{
+ if ( !pQuest )
+ return 0;
+
+ // loaners from the quest
+ const CUtlVector< CTFRequiredQuestItemsSet >& vecQuestRequiredItems = pQuest->GetItemDefinition()->GetQuestDef()->GetRequiredItemSets();
+ FOR_EACH_VEC( vecQuestRequiredItems, i )
+ {
+ // don't add dups
+ if ( vecLoanerItems.Find( vecQuestRequiredItems[i].GetLoanerItemDef() ) == vecLoanerItems.InvalidIndex() )
+ {
+ vecLoanerItems.AddToTail( vecQuestRequiredItems[i].GetLoanerItemDef() );
+ }
+ }
+
+ // loaners from the objectives
+ //{
+ // // Get all the objectives
+ // QuestObjectiveDefVec_t vecChosenObjectives;
+ // pQuest->GetItemDefinition()->GetQuestDef()->GetRolledObjectivesForItem( vecChosenObjectives, pQuest );
+
+ // // Get all the items we need to give as loaners from the objectives
+ // FOR_EACH_VEC( vecChosenObjectives, i )
+ // {
+ // const CUtlVector< CTFRequiredQuestItemsSet >& vecObjectiveRequiredItems = vecChosenObjectives[ i ]->GetConditions()->GetRequiredItemSets();
+ // FOR_EACH_VEC( vecObjectiveRequiredItems, iRequired )
+ // {
+ // // don't add dups
+ // if ( vecLoanerItems.Find( vecObjectiveRequiredItems[ iRequired ].GetLoanerItemDef() ) == vecLoanerItems.InvalidIndex() )
+ // {
+ // vecLoanerItems.AddToTail( vecObjectiveRequiredItems[ iRequired ].GetLoanerItemDef() );
+ // }
+ // }
+ // }
+ //}
+
+ return vecLoanerItems.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: fill vecGrantedLoaners with granted loaners from specific quest ID
+//-----------------------------------------------------------------------------
+static int GetLoanersFromLocalInventory( const itemid_t& questID, const CUtlVector< item_definition_index_t >& vecLoanerItems, CUtlVector< CEconItemView* >& vecGrantedLoaners )
+{
+ if ( vecLoanerItems.Count() > 0 )
+ {
+ CPlayerInventory *pLocalInv = InventoryManager()->GetLocalInventory();
+ int nCount = pLocalInv->GetItemCount();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ CEconItemView* pItem = pLocalInv->GetItem( i );
+
+ bool bIsLoaner = false;
+ // check if the item is a loaner and is associated with this quest
+ FOR_EACH_VEC( vecLoanerItems, iLoaner )
+ {
+ if ( vecLoanerItems[iLoaner] == pItem->GetItemDefIndex() && GetAssociatedQuestItemID( pItem ) == questID )
+ {
+ bIsLoaner = true;
+ break;
+ }
+ }
+
+ // already granted this loaner, remove from the list to give
+ if ( bIsLoaner )
+ {
+ vecGrantedLoaners.AddToTail( pItem );
+ }
+
+ // found all given loaners
+ if ( vecLoanerItems.Count() == vecGrantedLoaners.Count() )
+ {
+ break;
+ }
+ }
+ }
+
+ return vecGrantedLoaners.Count();
+}
+
+
+DECLARE_BUILD_FACTORY( CInputProxyPanel )
+CInputProxyPanel::CInputProxyPanel( Panel *parent, const char *pszPanelName )
+ : BaseClass( parent, pszPanelName )
+{}
+
+void CInputProxyPanel::AddPanelForCommand( EInputTypes eInputType, Panel* pPanel, const char* pszCommand )
+{
+ m_vecRedirectPanels[ eInputType ].AddToTail( { pPanel, pszCommand } );
+}
+
+void CInputProxyPanel::OnCursorMoved( int x, int y )
+{
+ FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_MOVE ], i )
+ {
+ PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_MOVE ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_MOVE ][ i ].m_pszCommand, "x", x, "y", y ) );
+ }
+}
+
+void CInputProxyPanel::OnCursorEntered()
+{
+ FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_ENTER ], i )
+ {
+ PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_ENTER ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_ENTER ][ i ].m_pszCommand ) );
+ }
+}
+
+void CInputProxyPanel::OnCursorExited()
+{
+ FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_EXIT ], i )
+ {
+ PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_EXIT ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_EXIT ][ i ].m_pszCommand ) );
+ }
+}
+
+void CInputProxyPanel::OnMousePressed(MouseCode code)
+{
+ FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_PRESS ], i )
+ {
+ PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_PRESS ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_PRESS ][ i ].m_pszCommand, "code", code ) );
+ }
+}
+
+void CInputProxyPanel::OnMouseDoublePressed(MouseCode code)
+{
+ FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_DOUBLE_PRESS ], i )
+ {
+ PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_DOUBLE_PRESS ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_DOUBLE_PRESS ][ i ].m_pszCommand, "code", code ) );
+ }
+}
+
+void CInputProxyPanel::OnMouseReleased(MouseCode code)
+{
+ FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_RELEASED ], i )
+ {
+ PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_RELEASED ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_RELEASED ][ i ].m_pszCommand, "code", code ) );
+ }
+}
+
+void CInputProxyPanel::OnMouseWheeled(int delta)
+{
+ FOR_EACH_VEC( m_vecRedirectPanels[ INPUT_MOUSE_WHEEL ], i )
+ {
+ PostMessage( m_vecRedirectPanels[ INPUT_MOUSE_WHEEL ][ i ].m_pPanel, new KeyValues( m_vecRedirectPanels[ INPUT_MOUSE_WHEEL ][ i ].m_pszCommand, "delta", delta ) );
+ }
+}
+
+
+DECLARE_BUILD_FACTORY( CQuestStatusPanel )
+
+CQuestStatusPanel::CQuestStatusPanel( Panel *parent, const char *pszPanelName )
+ : EditablePanel( parent, pszPanelName )
+ , m_pMovingContainer( NULL )
+ , m_bShouldBeVisible( false )
+{
+ m_pMovingContainer = new EditablePanel( this, "movingcontainer" );
+ m_transitionTimer.Invalidate();
+}
+
+void CQuestStatusPanel::SetShow( bool bShow )
+{
+ if ( bShow != m_bShouldBeVisible )
+ {
+ m_transitionTimer.Start( 0.6f );
+ }
+ m_bShouldBeVisible = bShow;
+ SetVisible( m_bShouldBeVisible );
+}
+
+void CQuestStatusPanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ const int nStartY = m_bShouldBeVisible ? m_iHiddenY : m_iVisibleY;
+ const int nEndY = m_bShouldBeVisible ? m_iVisibleY : m_iHiddenY;
+
+ float flProgress = 1.f;
+ if ( !m_transitionTimer.IsElapsed() )
+ {
+ flProgress = Bias( RemapValClamped( m_transitionTimer.GetElapsedTime(), 0.f , m_transitionTimer.GetCountdownDuration(), 0.f, 1.f ), 0.7f );
+ }
+ flProgress = RemapVal( flProgress, 0.f, 1.f, (float)nStartY, (float)nEndY );
+
+ m_pMovingContainer->SetPos( m_pMovingContainer->GetXPos(), flProgress );
+ SetVisible( m_bShouldBeVisible || flProgress > 0.f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestItemPanel::CQuestItemPanel( Panel *parent, const char *pszPanelName, CEconItemView* pQuestItem, CScrollableQuestList* pQuestList )
+ : EditablePanel( parent, pszPanelName )
+ , m_hQuestItem( NULL )
+ , m_eState( STATE_NORMAL )
+ , m_pTurnInContainer( NULL )
+ , m_pTurnInDimmer( NULL )
+ , m_pszCompleteSound( NULL )
+ , m_pFrontFolderContainer( NULL )
+ , m_pBackFolderContainer( NULL )
+ , m_bCollapsed( true )
+ , m_pQuestList( pQuestList )
+ , m_pQuestPaperContainer( NULL )
+ , m_pTitleButton( NULL )
+ , m_pIdentifyContainer( NULL )
+ , m_pIdentifyDimmer( NULL )
+ , m_pKVCipherStrings( NULL )
+ , m_pPhotoStatic( NULL )
+ , m_pFlavorScrollingContainer( NULL )
+ , m_pTurningInLabel( NULL )
+ , m_pFindServerButton( NULL )
+ , m_pLoanerContainerPanel( NULL )
+ , m_pRequestLoanerItemsButton( NULL )
+ , m_pEquipLoanerItemsButton( NULL )
+ , m_pItemTrackerPanel( NULL )
+ , m_pKVItemTracker( NULL )
+ , m_pObjectiveExplanationLabel( NULL )
+ , m_pEncodedStatus( NULL )
+ , m_pInactiveStatus( NULL )
+ , m_pReadyToTurnInStatus( NULL )
+ , m_pExpirationLabel( NULL )
+ , m_pTurnInButton( NULL )
+ , m_bHasAllControls( false )
+ , m_pDiscardButton( NULL )
+{
+ SetItem( pQuestItem );
+ m_StateTimer.Invalidate();
+
+ ListenForGameEvent( "quest_objective_completed" );
+ ListenForGameEvent( "player_spawn" );
+ ListenForGameEvent( "client_disconnect" );
+ ListenForGameEvent( "inventory_updated" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestItemPanel::~CQuestItemPanel()
+{
+ if ( m_pItemTrackerPanel )
+ {
+ m_pItemTrackerPanel->MarkForDeletion();
+ m_pItemTrackerPanel = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings ( pScheme );
+ AddActionSignalTarget( GetQuestLog() );
+ LoadResFileForCurrentItem();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::LoadResFileForCurrentItem()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ const char *pszResFile = "Resource/UI/quests/QuestItemPanel_Base.res";
+
+ if ( m_hQuestItem )
+ {
+ const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition();
+ // Get our quest theme
+ const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme();
+ if ( pTheme )
+ {
+ pszResFile = pTheme->GetQuestItemResFile();
+ }
+ }
+
+ KeyValues *pConditions = new KeyValues( "conditions" );
+ if ( pConditions )
+ {
+ char uilanguage[64];
+ uilanguage[0] = 0;
+ engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
+ char szCondition[64];
+ Q_snprintf( szCondition, sizeof( szCondition ), "if_%s", uilanguage );
+ AddSubKeyNamed( pConditions, szCondition );
+ }
+
+ SetMouseInputEnabled( true ); // Slam this to true. When panels get created, they'll inherit their parents' mouse enabled state
+ // and if we've been fiddling with it, we might accidently create all child panels with mouse input disabled.
+ // Setting this to true just before the controls are made gives them a chance to be mouse enabled if they want.
+ LoadControlSettings( pszResFile, NULL, NULL, pConditions );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strReset );
+
+ m_pMainContainer = FindControl<EditablePanel>( "MainContainer" );
+ if ( m_pMainContainer )
+ {
+ m_pMainContainer->AddActionSignalTarget( this );
+ }
+
+ m_pQuestPaperContainer = FindControl<EditablePanel>( "QuestPaperContainer", true );
+ m_pFrontFolderContainer = FindControl<EditablePanel>( "FrontFolderContainer", true );
+ Assert( m_pFrontFolderContainer );
+ if ( m_pFrontFolderContainer )
+ {
+ m_pFrontFolderImage = m_pFrontFolderContainer->FindControl<ImagePanel>( "FrontFolderImage", true );
+ Assert( m_pFrontFolderImage );
+
+ m_pEncodedStatus = m_pFrontFolderContainer->FindControl< CQuestStatusPanel >( "EncodedStatus", true );
+ m_pInactiveStatus = m_pFrontFolderContainer->FindControl< CQuestStatusPanel >( "InactiveStatus", true );
+ m_pReadyToTurnInStatus = m_pFrontFolderContainer->FindControl< CQuestStatusPanel >( "ReadyToTurnInStatus", true );
+ }
+ m_pBackFolderContainer = FindControl<EditablePanel>( "BackFolderContainer", true );
+ Assert( m_pBackFolderContainer );
+ if ( m_pBackFolderContainer )
+ {
+ m_pBackFolderImage = m_pBackFolderContainer->FindControl<ImagePanel>( "BackFolderImage", true );
+ Assert( m_pBackFolderImage );
+ }
+
+ if ( m_pQuestPaperContainer )
+ {
+#if defined( STAGING_ONLY ) || defined( DEBUG )
+ // don't do this in public
+ m_pDiscardButton = new CExButton( m_pQuestPaperContainer, "Discard", "Discard", this, "discard_quest" );
+ m_pDiscardButton->SetEnabled( true );
+ m_pDiscardButton->SizeToContents();
+ m_pDiscardButton->SetZPos( 101 );
+ m_pDiscardButton->SetPos( 70, 40 );
+ m_pDiscardButton->SetVisible( false );
+#endif // STAGING_ONLY || DEBUG
+
+ m_pFindServerButton = m_pQuestPaperContainer->FindControl< CExButton >( "FindServerButton", true );
+
+ m_pLoanerContainerPanel = m_pQuestPaperContainer->FindControl< EditablePanel >( "LoanerContainerPanel", true );
+ if ( m_pLoanerContainerPanel )
+ {
+ m_pRequestLoanerItemsButton = m_pLoanerContainerPanel->FindControl< CExButton >( "RequestLoanerItemsButton", true );
+ m_pEquipLoanerItemsButton = m_pLoanerContainerPanel->FindControl< CExButton >( "EquipLoanerItemsButton", true );
+ for ( int i = 0; i < ARRAYSIZE( m_pLoanerItemModelPanel ); ++i )
+ {
+ m_pLoanerItemModelPanel[i] = m_pLoanerContainerPanel->FindControl< CItemModelPanel >( CFmtStr( "Loaner%dItemModelPanel", i + 1 ), true );
+ }
+ }
+
+ m_pTitleButton = m_pQuestPaperContainer->FindControl<CExButton>( "TitleButton", true );
+ m_pIdentifyContainer = m_pQuestPaperContainer->FindControl<EditablePanel>( "IdentifyButtonContainer", true );
+ if ( m_pIdentifyContainer )
+ {
+ m_pIdentifyDimmer = m_pIdentifyContainer->FindControl<EditablePanel>( "Dimmer", true );
+ m_pIdentifyButton = m_pIdentifyContainer->FindControl<CExButton>( "IdentifyButton", true );
+ }
+ Assert( m_pIdentifyContainer );
+
+ m_pEncodedImage = m_pQuestPaperContainer->FindControl<ImagePanel>( "EncodedImage", true );
+
+ m_pPhotoStatic = m_pQuestPaperContainer->FindControl<ImagePanel>( "StaticPhoto", true );
+ Assert( m_pPhotoStatic );
+
+ m_pFlavorScrollingContainer = m_pQuestPaperContainer->FindControl<CExScrollingEditablePanel>( "ScrollableBottomContainer", true );
+ Assert( m_pFlavorScrollingContainer );
+
+ if ( m_pFlavorScrollingContainer )
+ {
+ m_pObjectiveExplanationLabel = m_pFlavorScrollingContainer->FindControl< Label >( "QuestObjectiveExplanation", true );
+ }
+
+ CInputProxyPanel* pInputProxy = m_pQuestPaperContainer->FindControl< CInputProxyPanel >( "PaperInputProxyPanel", true );
+ if ( pInputProxy )
+ {
+ // Make the scroller scroll
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_WHEEL, m_pFlavorScrollingContainer, "MouseWheeled" );
+
+ // Make the title glow
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, m_pTitleButton, "CursorEntered" );
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, m_pTitleButton, "CursorExited" );
+
+ // Capture clicks to expand/contract
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_RELEASED, this, "MouseReleased" );
+ }
+
+ m_pTurnInContainer = m_pQuestPaperContainer->FindControl< EditablePanel >( "TurnInContainer" );
+ Assert( m_pTurnInContainer );
+ if ( m_pTurnInContainer )
+ {
+ m_pTurnInDimmer = m_pTurnInContainer->FindControl< EditablePanel >( "Dimmer", true );
+ Assert( m_pTurnInContainer );
+
+ m_pTurnInButton = m_pTurnInContainer->FindControl< Button >( "TurnInButton", true );
+ Assert( m_pTurnInButton );
+
+ m_pTurnInSpinnerContainer = m_pTurnInContainer->FindControl< EditablePanel>( "TurnInSpinnerContainer", true );
+ Assert( m_pTurnInSpinnerContainer );
+
+ if ( m_pTurnInSpinnerContainer )
+ {
+ m_pTurningInLabel = m_pTurnInSpinnerContainer->FindControl< Label >( "TurningInLabel", true );
+ Assert( m_pTurningInLabel );
+ }
+ }
+
+ m_pAcceptedImage = m_pQuestPaperContainer->FindControl< ImagePanel >( "AcceptedImage", true );
+ Assert( m_pAcceptedImage );
+ }
+
+ if ( m_pFrontFolderContainer )
+ {
+ CInputProxyPanel* pInputProxy = m_pFrontFolderContainer->FindControl< CInputProxyPanel >( "FrontInputProxyPanel", true );
+ if ( pInputProxy )
+ {
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, m_pInactiveStatus, "CursorEntered" );
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, m_pInactiveStatus, "CursorExited" );
+
+ // Make the title glow
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, m_pTitleButton, "CursorEntered" );
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, m_pTitleButton, "CursorExited" );
+
+ // Make the backdrop highlight
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_ENTER, this, "CollapsedGlowStart" );
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_EXIT, this, "CollapsedGlowEnd" );
+
+ // Capture clicks to expand/contract
+ pInputProxy->AddPanelForCommand( CInputProxyPanel::INPUT_MOUSE_RELEASED, this, "MouseReleased" );
+ }
+ }
+
+ m_pExpirationLabel = FindControl<Label>( "QuestExpirationWarning", true );
+ m_pFlavorText = FindControl<Label>( "QuestFlavorText", true );
+
+ SetupObjectivesPanels( true );
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ }
+
+ m_bHasAllControls = m_pQuestPaperContainer
+ && m_pFrontFolderContainer
+ && m_pFrontFolderImage
+ && m_pBackFolderContainer
+ && m_pBackFolderImage
+ && m_pEncodedStatus
+ && m_pInactiveStatus
+ && m_pReadyToTurnInStatus
+ && m_pFlavorText
+ && m_pObjectiveExplanationLabel
+ && m_pExpirationLabel
+ && m_pTurnInContainer
+ && m_pTurnInDimmer
+ && m_pTurnInButton
+ && m_pIdentifyButton
+ && m_pTurnInSpinnerContainer
+ && m_pTitleButton
+ && m_pIdentifyDimmer
+ && m_pIdentifyContainer
+ && m_pPhotoStatic
+ && m_pAcceptedImage
+ && m_pTurningInLabel
+ && m_pFlavorScrollingContainer
+ && m_pItemTrackerPanel
+ && m_pEncodedImage
+ && m_pMainContainer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::ApplySettings( KeyValues *inResourceData )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ BaseClass::ApplySettings( inResourceData );
+
+ if ( m_hQuestItem )
+ {
+ m_vecFoldersImages.Purge();
+ KeyValues *pKVFoldersBlock = inResourceData->FindKey( "folders" );
+ Assert( pKVFoldersBlock );
+ if ( pKVFoldersBlock )
+ {
+ FOR_EACH_TRUE_SUBKEY( pKVFoldersBlock, pKVFolder )
+ {
+ auto& folder = m_vecFoldersImages[ m_vecFoldersImages.AddToTail() ];
+ folder.m_strFront = pKVFolder->GetString( "front", NULL);
+ folder.m_strBack = pKVFolder->GetString( "back", NULL );
+ }
+ }
+ else
+ {
+ const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition();
+ // Get our quest theme
+ const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme();
+ if ( pTheme )
+ {
+ Warning( "%s %s is missing 'folders' data\n", pItemDef->GetQuestDef()->GetCorrespondingOperationName(), pTheme->GetQuestItemResFile() );
+ }
+ }
+ }
+
+ if ( 1/*m_pKVItemTracker == NULL*/ )
+ {
+ KeyValues *pTrackerKV = inResourceData->FindKey( "tracker_kv" );
+
+ if ( pTrackerKV )
+ {
+ m_pKVItemTracker = pTrackerKV->MakeCopy();
+ }
+ }
+
+ m_strEncodedText = inResourceData->GetString( "encoded_text", NULL );
+ m_strExpireText = inResourceData->GetString( "expire_text", NULL );
+ m_strItemTrackerResFile = inResourceData->GetString( "TrackerPanelResFile", NULL );
+ // Sound effects
+ m_strTurnInSound = inResourceData->GetString( "turn_in_sound", NULL );
+ m_strTurnInSuccessSound = inResourceData->GetString( "turn_in_success_sound", NULL );
+ m_strDecodeSound = inResourceData->GetString( "decode_sound", NULL );
+ m_strExpandSound = inResourceData->GetString( "expand_sound", NULL );
+ m_strCollapseSound = inResourceData->GetString( "collapse_sound", NULL );
+
+ // Animations
+ m_strReset = inResourceData->GetString( "anim_reset", NULL );
+ m_strAnimExpand = inResourceData->GetString( "anim_expand", NULL );
+ m_strAnimCollapse = inResourceData->GetString( "anim_collapse", NULL );
+ m_strTurningIn = inResourceData->GetString( "anim_turning_in", NULL );
+ m_strHighlightOn = inResourceData->GetString( "anim_highlight_on", NULL );
+ m_strHighlightOff = inResourceData->GetString( "anim_highlight_off", NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::PerformLayout( void )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ BaseClass::PerformLayout();
+
+ if ( !HasAllControls() )
+ return;
+
+ m_pIdentifyContainer->SetVisible( m_eState == STATE_UNIDENTIFIED );
+ m_pTurnInContainer->SetVisible( m_eState == STATE_COMPLETED || m_eState == STATE_TURNING_IN__GC_RESPONDED || m_eState == STATE_TURNING_IN__WAITING_FOR_GC);
+ m_pTurnInButton->SetVisible( m_eState == STATE_COMPLETED );
+ m_pTurnInSpinnerContainer->SetVisible( m_eState == STATE_TURNING_IN__GC_RESPONDED || m_eState == STATE_TURNING_IN__WAITING_FOR_GC );
+ m_pPhotoStatic->SetVisible( m_eState == STATE_UNIDENTIFIED || m_eState == STATE_IDENTIFYING );
+ m_pFindServerButton->SetVisible( m_eState == STATE_NORMAL );
+
+ // only exist in non public build
+ if ( m_pDiscardButton )
+ {
+ m_pDiscardButton->SetVisible( m_eState == STATE_NORMAL );
+ }
+
+ // loaners
+ if ( m_eState == STATE_NORMAL || m_eState == STATE_COMPLETED )
+ {
+ // get all loaners required from quest
+ CUtlVector< item_definition_index_t > vecLoanerItems;
+ bool bRequiredLoaners = GetLoanerListFromQuest( m_hQuestItem, vecLoanerItems ) > 0;
+
+ // get all granted loaners from this quest
+ CUtlVector< CEconItemView* > vecGrantedLoaners;
+ if ( bRequiredLoaners )
+ {
+ GetLoanersFromLocalInventory( m_hQuestItem->GetItemID(), vecLoanerItems, vecGrantedLoaners );
+ }
+
+ if ( bRequiredLoaners )
+ {
+ m_pLoanerContainerPanel->SetVisible( true );
+ bool bAllGranted = vecLoanerItems.Count() == vecGrantedLoaners.Count();
+ m_pRequestLoanerItemsButton->SetVisible( !bAllGranted );
+ m_pEquipLoanerItemsButton->SetVisible( bAllGranted );
+
+ for ( int i = 0; i < ARRAYSIZE( m_pLoanerItemModelPanel ); ++i )
+ {
+ // try to use the granted items first
+ if ( i < vecGrantedLoaners.Count() )
+ {
+ m_pLoanerItemModelPanel[i]->SetItem( vecGrantedLoaners[i] );
+ m_pLoanerItemModelPanel[i]->SetVisible( true );
+ }
+ // In case we don't get all the loaner items, use fake items as second option
+ else if ( i < vecLoanerItems.Count() )
+ {
+ CEconItemView tempItem;
+ tempItem.Init( vecLoanerItems[i], AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+
+ m_pLoanerItemModelPanel[i]->SetItem( &tempItem );
+ m_pLoanerItemModelPanel[i]->SetVisible( true );
+ }
+ else
+ {
+ m_pLoanerItemModelPanel[i]->SetVisible( false );
+ }
+ }
+ }
+ else
+ {
+ m_pLoanerContainerPanel->SetVisible( false );
+ }
+ }
+ else
+ {
+ m_pLoanerContainerPanel->SetVisible( false );
+ }
+
+
+
+ m_pEncodedStatus->SetShow( m_eState == STATE_UNIDENTIFIED );
+ m_pReadyToTurnInStatus->SetShow( m_eState == STATE_COMPLETED || m_eState == STATE_TURNING_IN__GC_RESPONDED || m_eState == STATE_TURNING_IN__WAITING_FOR_GC );
+
+ float flDecodeAmount = 1.f;
+ // Only cypher-style decoding needs to decode
+ if ( m_eDecodeStyle == DECODE_STYLE_CYPHER && m_eState == STATE_UNIDENTIFIED )
+ {
+ flDecodeAmount = 0.f;
+ }
+
+ m_pEncodedImage->SetAlpha( m_eState == STATE_UNIDENTIFIED ? 255 : 0 );
+
+ if ( m_hQuestItem )
+ {
+ m_pTitleButton->SetText( GetDecodedString( "name", flDecodeAmount ) );
+
+ int nScrollableYOffset = 0;
+ // Check if the quest is going to expire soon (within a week). If so, show a "This is going to be destroyed" message.
+ const CRTime nExpirationTime = m_hQuestItem->GetExpirationDate();
+ const CRTime nOneWeekFromNow = CRTime::RTime32DateAdd( CRTime::RTime32TimeCur(), 1, k_ETimeUnitWeek );
+ const bool bExpiringSoon = nExpirationTime.GetRTime32() != RTime32(0) && nExpirationTime < nOneWeekFromNow;
+ m_pExpirationLabel->SetVisible( bExpiringSoon );
+ if ( bExpiringSoon )
+ {
+ CLocalizedRTime32 locTime = { nExpirationTime.GetRTime32(), false, GLocalizationProvider(), NULL };
+ m_pExpirationLabel->SetText( CConstructLocalizedString( g_pVGuiLocalize->Find( m_strExpireText ), locTime ) );
+ m_pExpirationLabel->InvalidateLayout( true );
+ m_pExpirationLabel->SizeToContents();
+ nScrollableYOffset += m_pExpirationLabel->GetTall();
+ }
+
+ m_pObjectiveExplanationLabel->SetPos( 0, nScrollableYOffset );
+ m_pObjectiveExplanationLabel->SetText( GetDecodedString( "explanation", flDecodeAmount ) );
+ m_pObjectiveExplanationLabel->InvalidateLayout( true ); // So we get the right height when we do SizeToContents below
+ m_pObjectiveExplanationLabel->SizeToContents();
+ nScrollableYOffset += m_pObjectiveExplanationLabel->GetTall();
+
+ m_pFlavorText->SetText( GetDecodedString( "desc", flDecodeAmount ) );
+ int nWide, nTall;
+ m_pFlavorText->GetTextImage()->GetContentSize( nWide, nTall );
+ m_pFlavorText->SetTall( nTall + 20 );
+
+ m_pItemTrackerPanel->SetPos( m_pItemTrackerPanel->GetXPos(), nScrollableYOffset );
+ nScrollableYOffset += m_pItemTrackerPanel->GetTall();
+
+ // Put the flavor text below the obectives
+ m_pFlavorText->SetPos( m_pFlavorText->GetXPos(), nScrollableYOffset );
+
+ m_pFlavorScrollingContainer->InvalidateLayout( true );
+ m_pFlavorScrollingContainer->InvalidateLayout();
+ m_pFlavorScrollingContainer->ResetScrollAmount();
+
+
+ // Randomize our folder images based on original ID
+ if ( m_vecFoldersImages.Count() )
+ {
+ RandomSeed( m_hQuestItem->GetSOCData() ? m_hQuestItem->GetSOCData()->GetOriginalID() : m_hQuestItem->GetItemDefIndex() );
+ int idx = RandomInt( 0, m_vecFoldersImages.Count() - 1 );
+
+ m_pFrontFolderImage->SetImage( m_vecFoldersImages[ idx ].m_strFront );
+ m_pBackFolderImage->SetImage( m_vecFoldersImages[ idx ].m_strBack );
+ }
+ }
+
+ UpdateInvalidReasons();
+
+ if ( m_pItemTrackerPanel )
+ {
+ auto& vecObjectives = m_pItemTrackerPanel->GetAttributePanels();
+ FOR_EACH_VEC( vecObjectives, i )
+ {
+ // Only do this when unidentified. The panel updates its own string, and we don't want to stomp it
+ if ( m_eState == STATE_UNIDENTIFIED )
+ {
+ const wchar_t *pszString = GetDecodedString( CFmtStr( "objective%d", i ), flDecodeAmount );
+ vecObjectives[ i ]->SetDialogVariable( "attr_desc", pszString );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestItemPanel::IsCursorOverMainContainer() const
+{
+ return m_pMainContainer ? m_pMainContainer->IsCursorOver() : false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::SetupObjectivesPanels( bool bRecreate )
+{
+ if ( m_pItemTrackerPanel && bRecreate )
+ {
+ m_pItemTrackerPanel->MarkForDeletion();
+ m_pItemTrackerPanel = NULL;
+ }
+
+ if ( !m_hQuestItem )
+ return;
+
+ if ( !m_pItemTrackerPanel )
+ {
+ m_pItemTrackerPanel = new CItemTrackerPanel( m_pFlavorScrollingContainer, "ItemTrackerPanel", m_hQuestItem->GetSOCData(), m_strItemTrackerResFile );
+ m_pItemTrackerPanel->SetAutoDelete( false );
+ SETUP_PANEL( m_pItemTrackerPanel );
+ }
+ else
+ {
+ // Get all the panels created
+ m_pItemTrackerPanel->SetItem( m_hQuestItem->GetSOCData() );
+ m_pItemTrackerPanel->InvalidateLayout( true );
+ }
+
+ m_pItemTrackerPanel->SetTall( m_pItemTrackerPanel->GetContentTall() );
+
+ // Need to re-layout so the flavor text gets properly positioned under
+ // all of the objectives
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::SetItem( CEconItemView* pItem )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ if ( pItem == m_hQuestItem )
+ {
+ return;
+ }
+
+ m_bCollapsed = true;
+ m_hQuestItem.SetItem( pItem );
+
+ if ( m_pItemTrackerPanel && pItem )
+ {
+ m_pItemTrackerPanel->SetItem( pItem->GetSOCData() );
+ }
+
+ // By default
+ SetState( STATE_NORMAL );
+
+ if ( m_hQuestItem )
+ {
+ if ( IsQuestItemReadyToTurnIn( m_hQuestItem ) )
+ {
+ SetState( STATE_COMPLETED );
+ }
+ else if ( IsUnacknowledged() )
+ {
+ SetState( STATE_UNIDENTIFIED );
+ }
+
+ // Snag the quickplay map (if there is one)
+ m_strQuickPlayMap = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetQuickplayMapName();
+
+ m_strMatchmakingGroupName = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMatchmakingGroupName();
+ m_strMatchmakingCategoryName = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMatchmakingCategoryName();
+ m_strMatchmakingMapName = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMatchmakingMapName();
+ }
+
+ // Reload res file so we get the right art
+ LoadResFileForCurrentItem();
+
+ // Capture strings after controls are created
+ CaptureAndEncodeStrings();
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns if the character is one that we don't want to re-encode
+// as another, or one that we don't want to encode another to in
+// order to maintain line breaks so that the decoding sequence is
+// easier for the user to follow.
+//-----------------------------------------------------------------------------
+bool IsNonEncodeCharacter( const wchar_t& wch)
+{
+ switch ( wch )
+ {
+ case L'\x000A':
+ case L'\x000B':
+ case L'\x000C':
+ case L'\x000D':
+ case L'\x0085':
+ case L'\x2028':
+ case L'\x2029':
+ case L' ':
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::CaptureAndEncodeStrings()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !m_hQuestItem )
+ return;
+
+ // Clean up any existing values
+ if ( m_pKVCipherStrings )
+ {
+ m_pKVCipherStrings->deleteThis();
+ m_pKVCipherStrings = NULL;
+ }
+
+ m_pKVCipherStrings = new KeyValues( "cipherstrings" );
+ KeyValues *pKVDecoded = m_pKVCipherStrings->CreateNewKey();
+ pKVDecoded->SetName( "decoded" );
+
+ {
+ // Capture the description/flavor string
+ const char *pszLocToken = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetRolledDescriptionForItem( m_hQuestItem->GetSOCData() );
+ pKVDecoded->SetWString( "desc", g_pVGuiLocalize->Find( pszLocToken ) );
+ }
+
+ if ( m_pObjectiveExplanationLabel )
+ {
+ wchar_t wszBuff[512];
+ m_pObjectiveExplanationLabel->GetText( wszBuff, ARRAYSIZE( wszBuff ) );
+ pKVDecoded->SetWString( "explanation", wszBuff );
+ }
+
+ auto& vecObjectives = m_pItemTrackerPanel->GetAttributePanels();
+ // Capture objective strings
+ FOR_EACH_VEC( vecObjectives, i )
+ {
+ CItemAttributeProgressPanel *pObjective = vecObjectives[ i ];
+ KeyValues *pKV = pObjective->GetDialogVariables();
+ pKVDecoded->SetWString( CFmtStr( "objective%d", i ), pKV->GetWString( "attr_desc" ) );
+ }
+
+ // Create encoded strings from the decoded strings
+ KeyValues *pKVEncoded = pKVDecoded->MakeCopy();
+ pKVEncoded->SetName( "encoded" );
+
+ m_pKVCipherStrings->AddSubKey( pKVEncoded );
+
+ RandomSeed( m_hQuestItem->GetSOCData() ? m_hQuestItem->GetSOCData()->GetOriginalID() : m_hQuestItem->GetItemDefIndex() );
+
+ // "encode" each string by scrambling
+ FOR_EACH_VALUE( pKVEncoded, pKVString )
+ {
+ const wchar_t *pWString = pKVString->GetWString();
+ wchar wszBuff[4096];
+ loc_scpy_safe( wszBuff, pWString );
+ int nStrLen = Q_wcslen( wszBuff );
+
+ // Go through the entire string and swap each character
+ // with another random character in the string
+ int i=0;
+ while( wszBuff[i] != 0 && i < ARRAYSIZE( wszBuff ) )
+ {
+ // Dont scramble spaces to maintain line breaks
+ if ( !IsNonEncodeCharacter( wszBuff[i] ) )
+ {
+ // Scramble, but keep trying if we scramble to a space
+ do
+ {
+ wszBuff[i] = *(pWString + RandomInt( 0, nStrLen - 1 ) );
+ } while ( IsNonEncodeCharacter( wszBuff[i] ) );
+
+ }
+
+ i++;
+ }
+
+ pKVEncoded->SetWString( pKVString->GetName(), wszBuff );
+ }
+
+ const char *pszLocToken = m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetRolledNameForItem( m_hQuestItem->GetSOCData() );
+ const wchar_t* pwszName = g_pVGuiLocalize->Find( pszLocToken );
+ // Force the encrypted version of the quest title to be "<Encrypted>".
+ pKVEncoded->SetWString( "name", g_pVGuiLocalize->Find( m_strEncodedText ) );
+ pKVDecoded->SetWString( "name", pwszName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnCommand( const char *command )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ BaseClass::OnCommand( command );
+
+ if ( FStrEq( command, "discard_quest" ) )
+ {
+ OnDiscardQuest();
+ }
+ else if ( FStrEq( command, "select" ) )
+ {
+ m_pQuestList->SetSelected( this, false );
+ }
+ else if ( FStrEq( command, "turnin" ) )
+ {
+ if ( m_hQuestItem && m_hQuestItem->GetItemDefinition() && m_hQuestItem->GetItemDefinition()->GetQuestDef() )
+ {
+ if ( !tf_quest_turn_in_confirm_opt_out.GetBool() && ( GetEarnedBonusPoints( m_hQuestItem ) != m_hQuestItem->GetItemDefinition()->GetQuestDef()->GetMaxBonusPoints() ) )
+ {
+ CTFGenericConfirmOptOutDialog *pPanel = ShowConfirmOptOutDialog( "#TF_Quest_TurnIn_Title", "#TF_Quest_TurnIn_Text",
+ "#TF_Quest_TurnIn_Yes", "#TF_Quest_TurnIn_No",
+ "#TF_Quest_TurnIn_Ask_Opt_Out", "tf_quest_turn_in_confirm_opt_out",
+ ConfirmTurnInQuest );
+ if ( pPanel )
+ {
+ pPanel->SetContext( this );
+ return;
+ }
+ }
+ else
+ {
+ OnCompleteQuest();
+ }
+ }
+ }
+ else if ( FStrEq( command, "identify" ) )
+ {
+ OnIdentify();
+ }
+ else if ( FStrEq( command, "request_loaner_items" ) )
+ {
+ GCSDK::CProtoBufMsg< CMsgGCQuestObjective_RequestLoanerItems > msg( k_EMsgGCQuestObjective_RequestLoanerItems );
+ msg.Body().set_quest_item_id( m_hQuestItem->GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ else if ( FStrEq( command, "equip_loaner_items" ) )
+ {
+ OnEquipLoaners();
+ }
+ else if( Q_strnicmp( "playsound", command, 9 ) == 0 )
+ {
+ vgui::surface()->PlaySound( command + 10 );
+ }
+ else if ( FStrEq( "mm_casual_open", command ) )
+ {
+ if ( GTFGCClientSystem() )
+ {
+ if ( ( m_strMatchmakingGroupName != 0 ) || ( m_strMatchmakingCategoryName != 0 ) || ( m_strMatchmakingMapName != 0 ) )
+ {
+ GTFGCClientSystem()->ClearCasualSearchCriteria();
+
+ if ( m_strMatchmakingGroupName != 0 )
+ {
+ int iGroupType = StringFieldToInt( m_strMatchmakingGroupName.Get(), s_pszMMTypes, ARRAYSIZE( s_pszMMTypes ) );
+ if ( iGroupType > -1 )
+ {
+ SelectGroup( (EMatchmakingGroupType)iGroupType, true );
+ }
+ }
+
+ if ( m_strMatchmakingCategoryName != 0 )
+ {
+ int iCategoryType = StringFieldToInt( m_strMatchmakingCategoryName.Get(), s_pszGameModes, ARRAYSIZE( s_pszGameModes ) );
+ if ( iCategoryType > -1 )
+ {
+ SelectCategory( (EGameCategory)iCategoryType, true );
+ }
+ }
+
+ if ( m_strMatchmakingMapName != 0 )
+ {
+ if ( GetItemSchema() )
+ {
+ const MapDef_t *pMap = GetItemSchema()->GetMasterMapDefByName( m_strMatchmakingMapName.Get() );
+ if ( pMap )
+ {
+ GTFGCClientSystem()->SelectCasualMap( pMap->m_nDefIndex, true );
+ }
+ }
+ }
+ }
+ }
+
+ // Defaulting to 12v12
+ GTFGCClientSystem()->SetLadderType( k_nMatchGroup_Casual_12v12 );
+ PromptOrFireCommand( "OpenMatchmakingLobby casual" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const wchar_t* CQuestItemPanel::GetDecodedString( const char* pszKeyName, float flPercentDecoded )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ static wchar_t wszBuff[4096];
+ KeyValues *pKVEncoded = m_pKVCipherStrings->FindKey( "encoded" );
+ KeyValues *pKVDecoded = m_pKVCipherStrings->FindKey( "decoded" );
+
+ // Trivial work?
+ if ( flPercentDecoded <= 0.f )
+ {
+ return pKVEncoded->GetWString( pszKeyName );
+ }
+ else if ( flPercentDecoded >= 1.f )
+ {
+ return pKVDecoded->GetWString( pszKeyName );
+ }
+
+ loc_scpy_safe( wszBuff, pKVEncoded->GetWString( pszKeyName ) );
+ const locchar_t* pwszDecoded = pKVDecoded->GetWString( pszKeyName );
+ int nLength = loc_strlen( pwszDecoded );
+ int nMaxCopy = nLength * flPercentDecoded;
+ // Not using V_wcsncpy because it null terminates.
+ wcsncpy( wszBuff, pwszDecoded, nMaxCopy );
+
+ return wszBuff;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnThink()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ switch ( m_eState )
+ {
+ case STATE_IDENTIFYING:
+ {
+ // Have we finished?
+ if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() )
+ {
+ m_pItemTrackerPanel->InvalidateLayout();
+ m_StateTimer.Invalidate();
+ SetState( STATE_NORMAL );
+
+ // Play a reveal sound?
+ const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition();
+ const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme();
+ if ( pTheme )
+ {
+ const char *pszRevealSound = pTheme->GetRevealSound();
+ if ( pszRevealSound && pszRevealSound[0] )
+ {
+ vgui::surface()->PlaySound( pszRevealSound );
+ }
+ }
+ }
+
+ float flPercent = m_StateTimer.GetElapsedTime() / m_StateTimer.GetCountdownDuration();
+
+ switch( m_eDecodeStyle )
+ {
+ case DECODE_STYLE_CYPHER:
+ {
+ // Slowly "decode" the text in the lables
+ m_pTitleButton->SetText( GetDecodedString( "name", flPercent ) );
+ m_pFlavorText->SetText( GetDecodedString( "desc", flPercent ) );
+ m_pObjectiveExplanationLabel->SetText( GetDecodedString( "explanation", flPercent ) );
+
+ auto& vecObjectives = m_pItemTrackerPanel->GetAttributePanels();
+ FOR_EACH_VEC( vecObjectives, i )
+ {
+ const wchar_t *pszString = GetDecodedString( CFmtStr( "objective%d", i ), flPercent );
+ vecObjectives[ i ]->SetDialogVariable( "attr_desc", pszString );
+ }
+ break;
+ }
+ case DECODE_STYLE_PANEL_FADE:
+ {
+ // Slowly fade out the encode image
+ m_pEncodedImage->SetAlpha( 255 * ( 1.f - flPercent ) );
+ break;
+ }
+ default:
+ Assert( 0 );
+ }
+
+ break;
+ }
+ case STATE_TURNING_IN__WAITING_FOR_GC:
+ {
+ // Have we finished?
+ if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() )
+ {
+ m_pQuestList->SetCompletingPanel( NULL );
+ m_StateTimer.Invalidate();
+
+ // Bring up confirm dialog
+ CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#TF_Trading_Timeout_Title", "#TF_Trading_Timeout_Text", "#TF_OK", NULL, NULL, NULL );
+
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ pDialog->Show();
+ }
+
+ SetState( STATE_COMPLETED );
+ }
+
+ // Intentionally fall through
+ }
+ case STATE_TURNING_IN__GC_RESPONDED:
+ {
+ // Have we finished?
+ if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() )
+ {
+ SetState( STATE_SHOW_ACCEPTED );
+ m_StateTimer.Start( 3.f );
+ m_pAcceptedImage->SetVisible( true );
+
+ vgui::surface()->PlaySound( m_strTurnInSuccessSound );
+ }
+
+ if ( m_pTurningInLabel )
+ {
+ int nPeriods = m_StateTimer.GetElapsedTime() / 0.3f;
+ nPeriods %= 4; // Only do up to 3 periods
+
+ wchar_t wszTurningInText[64];
+ char szTurningInLocToken[128];
+ m_pTurningInLabel->GetTextImage()->GetUnlocalizedText( szTurningInLocToken, ARRAYSIZE( szTurningInLocToken ) );
+ V_snwprintf( wszTurningInText, ARRAYSIZE( wszTurningInText ), L"%ls", g_pVGuiLocalize->Find( szTurningInLocToken ) );
+
+ while ( nPeriods > 0 )
+ {
+ V_wcsncat( wszTurningInText, L".", ARRAYSIZE( wszTurningInText ) );
+ --nPeriods;
+ }
+
+ m_pTurningInLabel->SetText( wszTurningInText );
+ }
+
+ break;
+ }
+ case STATE_SHOW_ACCEPTED:
+ {
+ if ( m_StateTimer.HasStarted() && m_StateTimer.IsElapsed() )
+ {
+ m_pQuestList->SetCompletingPanel( NULL );
+ m_StateTimer.Invalidate();
+
+ engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" );
+
+ InventoryManager()->ShowItemsPickedUp( true, false );
+ GetQuestLog()->AttachToGameUI();
+ GetQuestLog()->MarkQuestsDirty();
+ m_pQuestList->PopulateQuestLists();
+
+ engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" );
+
+ if ( m_pszCompleteSound )
+ {
+ vgui::surface()->PlaySound( m_pszCompleteSound );
+ }
+ }
+ else
+ {
+ float flPercent = Clamp( m_StateTimer.GetElapsedTime() / 0.2f, 0.f, 1.f );
+ m_nPaperXShakePos = sin( m_StateTimer.GetElapsedTime() * 200.f ) * ( 1.f - flPercent ) * 8.f;
+ m_nPaperYShakePos = sin( m_StateTimer.GetElapsedTime() * 200.f ) * ( 1.f - flPercent ) * 8.f;
+ m_pQuestPaperContainer->SetPos( m_nPaperXPos + m_nPaperXShakePos, m_nPaperYPos + m_nPaperYShakePos );
+ }
+
+ break;
+ }
+ case STATE_UNIDENTIFIED:
+ {
+ // Do nothing
+ break;
+ }
+ case STATE_COMPLETED:
+ {
+ // Do nothing
+ break;
+ }
+ default:
+ // Do nothing
+ break;
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::FireGameEvent( IGameEvent *event )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ if( FStrEq( event->GetName(), "quest_objective_completed" ) )
+ {
+ itemid_t nIDLow = 0x00000000FFFFFFFF & (itemid_t)event->GetInt( "quest_item_id_low" );
+ itemid_t nIDHi = 0xFFFFFFFF00000000 & (itemid_t)event->GetInt( "quest_item_id_hi" ) << 32;
+ itemid_t nID = nIDLow | nIDHi;
+ if ( m_hQuestItem && nID == m_hQuestItem->GetID() )
+ {
+ SetupObjectivesPanels( false );
+
+ if ( IsQuestItemReadyToTurnIn( m_hQuestItem ) )
+ {
+ SetState( STATE_COMPLETED );
+ }
+
+ PerformLayout();
+ }
+ }
+ else if ( FStrEq( event->GetName(), "player_spawn" )
+ || FStrEq( event->GetName(), "client_disconnect" ) )
+ {
+ InvalidateLayout();
+ }
+ else if ( FStrEq( "inventory_updated", event->GetName() ) )
+ {
+ // InvalidateLayout();
+ }
+}
+
+void CQuestItemPanel::OnMouseReleased( MouseCode code )
+{
+ OnCommand( "select" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update our invalid reasons
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::UpdateInvalidReasons()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ InvalidReasonsContainer_t invalidReasons;
+ bool bAllAreInvalid = false;
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer && m_hQuestItem )
+ {
+ // Get the tracker for the items
+ const CQuestItemTracker* pItemTracker = QuestObjectiveManager()->GetTypedTracker< CQuestItemTracker* >( m_hQuestItem->GetItemID() );
+ // Get invalid reasons
+ if ( pItemTracker )
+ {
+ int nNumInvalid = pItemTracker->IsValidForPlayer( pLocalPlayer, invalidReasons );
+ bAllAreInvalid = pItemTracker->GetTrackers().Count() == nNumInvalid;
+ }
+
+ // Build a string describing why the current quest can't be worked on
+ if ( !invalidReasons.IsValid() )
+ {
+ CUtlVector< CUtlString > vecStrings;
+ // Get the strings that explain each reasons
+ GetInvalidReasonsNames( invalidReasons, vecStrings );
+
+ wchar_t wszBuff[ 1024 ];
+
+ // Start with the explanation
+ V_swprintf_safe( wszBuff, L"%ls", g_pVGuiLocalize->Find( "#TF_QuestInvalid_Explanation" ) );
+
+ // Add in each reason why the quest is invalid
+ for( int i = 0; i < vecStrings.Count(); ++ i )
+ {
+ V_wcscat_safe( wszBuff, L"\n\n" );
+ V_wcscat_safe( wszBuff, g_pVGuiLocalize->Find( vecStrings[i] ) );
+ }
+
+ // This gets snagged by CQuestTooltip
+ m_pInactiveStatus->SetDialogVariable( "tiptext", wszBuff );
+ }
+ }
+
+ // Visible if there's a reason why we're invalid
+ bool bShow = bAllAreInvalid && m_eState == STATE_NORMAL;
+ m_pInactiveStatus->SetShow( bShow );
+ m_pInactiveStatus->SetMouseInputEnabled( bShow );
+ m_pInactiveStatus->SetTooltip( g_spTextTooltip, NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start a glow
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnCollapsedGlowStart( void )
+{
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strHighlightOn );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop the glow
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnCollapsedGlowEnd( void )
+{
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strHighlightOff );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Delete the quest.
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnDiscardQuest( void )
+{
+#if !defined(STAGING_ONLY) && !defined(DEBUG)
+ // Not in public!
+ return;
+#endif
+
+ if ( m_pQuestList->GetCompletingPanel() == NULL )
+ {
+ // Bring up confirm dialog
+ CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#QuestConfirmDiscard_Title", "#QuestConfirmDiscard_Body", "#X_DiscardItem", "#Cancel", &ConfirmDiscardQuest, NULL );
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ pDialog->Show();
+ }
+
+ const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition();
+ // Get our quest theme
+ const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme();
+ if ( pTheme )
+ {
+ const char *pszDiscardSound = pTheme->GetDiscardSound();
+ if ( pszDiscardSound && pszDiscardSound[0] )
+ {
+ vgui::surface()->PlaySound( pszDiscardSound );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Equip loaners for local player
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnEquipLoaners( void )
+{
+ if ( !m_hQuestItem )
+ return;
+
+ if ( m_pQuestList->GetCompletingPanel() == NULL )
+ {
+ // Bring up confirm dialog
+ CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#QuestConfirmEquipLoaners_Title", "#QuestConfirmEquipLoaners_Body", "#Equip", "#Cancel", &ConfirmEquipLoaners, NULL );
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ pDialog->Show();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Send a message to the GC to evaluate completion of this quest
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnCompleteQuest( void )
+{
+ if ( !m_hQuestItem )
+ return;
+
+ // Double check that they're not just forcing the command
+ if ( IsQuestItemReadyToTurnIn( m_hQuestItem ) && m_pQuestList->GetCompletingPanel() == NULL )
+ {
+ m_pQuestList->SetCompletingPanel( this );
+
+ SetState( STATE_TURNING_IN__WAITING_FOR_GC );
+
+ // Use the timer for turning in the quest
+ m_StateTimer.Start( k_flQuestTurnInTime );
+ vgui::surface()->PlaySound( m_strTurnInSound );
+
+ GCSDK::CProtoBufMsg< CMsgGCQuestComplete_Request > msg( k_EMsgGCQuestComplete_Request );
+
+ msg.Body().set_quest_item_id( m_hQuestItem->GetItemID() );
+
+ GCClientSystem()->BSendMessage( msg );
+
+ PostActionSignal( new KeyValues("CompleteQuest") );
+
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strTurningIn );
+
+ const GameItemDefinition_t *pItemDef = m_hQuestItem->GetItemDefinition();
+ // Get our quest theme
+ const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme();
+ if ( pTheme )
+ {
+ m_pszCompleteSound = pTheme->GetRewardSound();
+ }
+ }
+}
+
+void CQuestItemPanel::OnIdentify()
+{
+ if ( IsUnacknowledged() )
+ {
+ SetState( STATE_IDENTIFYING );
+
+ // Use the timer for identifying progress
+ m_StateTimer.Start( k_flQuestDecodeTime );
+ vgui::surface()->PlaySound( m_strDecodeSound );
+
+ // ack item
+ CEconItemView *pModifyItem = m_hQuestItem;
+ TFInventoryManager()->AcknowledgeItem( pModifyItem, false );
+ TFInventoryManager()->SetItemBackpackPosition( pModifyItem, (uint32)-1, false, true );
+
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pQuestPaperContainer, "QuestItem_StaticPhoto_Reveal" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnConfirmDelete( bool bConfirm )
+{
+ // Delete the quest
+ if ( bConfirm && m_hQuestItem )
+ {
+ GCSDK::CProtoBufMsg< CMsgGCQuestDiscard_Request > msg( k_EMsgGCQuestDiscard_Request );
+
+ msg.Body().set_quest_item_id( m_hQuestItem->GetItemID() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::OnConfirmEquipLoaners( bool bConfirm )
+{
+ // equip loaners
+ if ( bConfirm && m_hQuestItem )
+ {
+ // get all loaners required from quest
+ CUtlVector< item_definition_index_t > vecLoanerItems;
+ bool bRequiredLoaners = GetLoanerListFromQuest( m_hQuestItem, vecLoanerItems );
+
+ // get all granted loaners from this quest
+ CUtlVector< CEconItemView* > vecGrantedLoaners;
+ if ( bRequiredLoaners )
+ {
+ GetLoanersFromLocalInventory( m_hQuestItem->GetItemID(), vecLoanerItems, vecGrantedLoaners );
+ }
+
+ for ( int i=0; i<vecGrantedLoaners.Count(); ++i )
+ {
+ CEconItemView *pItem = vecGrantedLoaners[i];
+ if ( pItem )
+ {
+ // do it for first class that can equip
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; ++iClass )
+ {
+ if ( pItem->GetStaticData()->CanBeUsedByClass( iClass ) )
+ {
+ int iSlot = pItem->GetStaticData()->GetLoadoutSlot( iClass );
+ TFInventoryManager()->EquipItemInLoadout( iClass, iSlot, pItem->GetItemID() );
+
+ // take the player to character loadout page
+ engine->ClientCmd_Unrestricted( CFmtStr( "open_charinfo_direct %d", iClass ) );
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::QuestCompletedResponse()
+{
+ // If we werent the one listening, dont bother
+ if ( m_eState != STATE_TURNING_IN__WAITING_FOR_GC )
+ return;
+
+ m_pQuestPaperContainer->GetPos( m_nPaperXPos, m_nPaperYPos );
+ m_nPaperXShakePos = m_nPaperYShakePos = 0;
+
+ SetState( STATE_TURNING_IN__GC_RESPONDED );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestItemPanel::SetSelected( bool bSelected, bool bImmediate )
+{
+ bool bPrevCollapsedSide = m_bCollapsed;
+ m_bCollapsed = ( !m_bCollapsed && bSelected ) || ( !bSelected );
+
+ if ( !bImmediate && bPrevCollapsedSide != m_bCollapsed )
+ {
+ if ( m_bCollapsed )
+ {
+ vgui::surface()->PlaySound( m_strCollapseSound );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strAnimCollapse );
+ }
+ else
+ {
+ vgui::surface()->PlaySound( m_strExpandSound );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strAnimExpand );
+ }
+ }
+ else
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_strReset );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestItemPanel::IsUnacknowledged()
+{
+ if ( !m_hQuestItem )
+ return false;
+
+ return IsQuestItemUnidentified( m_hQuestItem->GetSOCData() );
+}
+
+void CQuestItemPanel::SetState( EItemPanelState_t eState )
+{
+ m_eState = eState;
+ InvalidateLayout();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to handle a loaner item response
+//-----------------------------------------------------------------------------
+class CGCLoanerRequestResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCLoanerRequestResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgGCQuestObjective_RequestLoanerResponse> msg( pNetPacket );
+
+ // Show them the items they just got loaned!
+ InventoryManager()->ShowItemsPickedUp( true, false );
+
+ return true;
+ }
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCLoanerRequestResponse, "CGCLoanerRequestResponse", k_EMsgGCQuestObjective_RequestLoanerResponse, GCSDK::k_EServerTypeGCClient );
diff --git a/game/client/tf/vgui/quest_item_panel.h b/game/client/tf/vgui/quest_item_panel.h
new file mode 100644
index 0000000..dfdb5a8
--- /dev/null
+++ b/game/client/tf/vgui/quest_item_panel.h
@@ -0,0 +1,265 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef QUEST_ITEM_PANEL_H
+#define QUEST_ITEM_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "econ_item_inventory.h"
+#include "tf_controls.h"
+
+using namespace vgui;
+
+class CScrollableQuestList;
+class CItemModelPanel;
+
+//-----------------------------------------------------------------------------
+// Simple tooltip class that looks into the moused-over panel's dialog variables
+// for "tiptext" and uses that value as its string to present.
+//-----------------------------------------------------------------------------
+class CQuestTooltip : public CTFTextToolTip
+{
+ DECLARE_CLASS_SIMPLE( CQuestTooltip, CTFTextToolTip );
+public:
+ CQuestTooltip( vgui::Panel *parent, const char *text = NULL )
+ : BaseClass( parent, text )
+ {}
+
+ virtual void ShowTooltip( Panel *pCurrentPanel ) OVERRIDE;
+ virtual void PositionWindow( Panel *pTipPanel ) OVERRIDE;
+private:
+};
+
+//-----------------------------------------------------------------------------
+// Can pass various input events to other panels
+//-----------------------------------------------------------------------------
+class CInputProxyPanel : public EditablePanel
+{
+public:
+
+ enum EInputTypes
+ {
+ INPUT_MOUSE_ENTER = 0,
+ INPUT_MOUSE_EXIT,
+ INPUT_MOUSE_PRESS,
+ INPUT_MOUSE_DOUBLE_PRESS,
+ INPUT_MOUSE_RELEASED,
+ INPUT_MOUSE_WHEEL,
+ INPUT_MOUSE_MOVE,
+ NUM_INPUT_TYPES,
+ };
+
+ DECLARE_CLASS_SIMPLE( CInputProxyPanel, EditablePanel );
+ CInputProxyPanel( Panel *parent, const char *pszPanelName );
+
+ void AddPanelForCommand( EInputTypes eInputType, Panel* pPanel, const char* pszCommand );
+
+ MESSAGE_FUNC_INT_INT( OnCursorMoved, "OnCursorMoved", x, y );
+ virtual void OnCursorEntered();
+ virtual void OnCursorExited();
+ virtual void OnMousePressed(MouseCode code);
+ virtual void OnMouseDoublePressed(MouseCode code);
+ virtual void OnMouseReleased(MouseCode code);
+ virtual void OnMouseWheeled(int delta);
+
+private:
+
+ struct CommandPair_t
+ {
+ Panel* m_pPanel;
+ const char* m_pszCommand;
+ };
+ CUtlVector< CommandPair_t > m_vecRedirectPanels[NUM_INPUT_TYPES];
+};
+
+//-----------------------------------------------------------------------------
+// Contains a panel that animates into place when it needs to show or hide
+//-----------------------------------------------------------------------------
+class CQuestStatusPanel : public EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CQuestStatusPanel, EditablePanel );
+ CQuestStatusPanel( Panel *parent, const char *pszPanelName );
+
+ void SetShow( bool bShow );
+ virtual void OnThink() OVERRIDE;
+
+private:
+ EditablePanel* m_pMovingContainer;
+ RealTimeCountdownTimer m_transitionTimer;
+ bool m_bShouldBeVisible;
+
+ CPanelAnimationVarAliasType( int, m_iVisibleY, "visible_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iHiddenY, "hidden_y", "40", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// An representation of a single quest
+//-----------------------------------------------------------------------------
+class CQuestItemPanel : public EditablePanel, CGameEventListener
+{
+public:
+ enum EItemPanelState_t
+ {
+ STATE_NORMAL = 0,
+ STATE_UNIDENTIFIED,
+ STATE_IDENTIFYING,
+ STATE_COMPLETED,
+ STATE_TURNING_IN__WAITING_FOR_GC,
+ STATE_TURNING_IN__GC_RESPONDED,
+ STATE_SHOW_ACCEPTED,
+
+ NUM_STATES,
+ };
+
+ DECLARE_CLASS_SIMPLE( CQuestItemPanel, EditablePanel );
+
+ CQuestItemPanel( Panel *parent, const char *pszPanelName, CEconItemView* pQuestItem, CScrollableQuestList* pQuestList );
+ virtual ~CQuestItemPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+ virtual void OnSizeChanged(int wide, int tall) OVERRIDE {}
+ virtual void OnMouseReleased(MouseCode code) OVERRIDE;
+
+ const CEconItemView* GetItem() { return m_hQuestItem; }
+ void SetItem( CEconItemView* pItem );
+ void QuestCompletedResponse();
+ EItemPanelState_t GetState() const { return m_eState; }
+ void SetSelected( bool bSelected, bool bImmediate );
+ bool IsSelected() const { return !m_bCollapsed; }
+ bool IsCursorOverMainContainer() const;
+
+ MESSAGE_FUNC( OnCollapsedGlowStart, "CollapsedGlowStart" );
+ MESSAGE_FUNC( OnCollapsedGlowEnd, "CollapsedGlowEnd" );
+ MESSAGE_FUNC( OnDiscardQuest, "DiscardQuest" );
+ MESSAGE_FUNC( OnEquipLoaners, "EquipLoaners" );
+ void OnCompleteQuest();
+ void OnConfirmDelete( bool bConfirm );
+ void OnConfirmEquipLoaners( bool bConfirm );
+
+protected:
+
+ bool HasAllControls() const { return m_bHasAllControls; }
+
+ void LoadResFileForCurrentItem();
+ void OnIdentify();
+ void SetupObjectivesPanels( bool bRecreate );
+ bool IsUnacknowledged();
+ void SetState( EItemPanelState_t eState );
+ void CaptureAndEncodeStrings();
+ const wchar_t* GetDecodedString( const char* pszKeyName, float flPercentDecoded );
+ void UpdateInvalidReasons();
+
+ EItemPanelState_t m_eState;
+ CEconItemViewHandle m_hQuestItem;
+
+ EditablePanel *m_pQuestPaperContainer;
+ EditablePanel *m_pFrontFolderContainer;
+ ImagePanel *m_pFrontFolderImage;
+ EditablePanel *m_pBackFolderContainer;
+ ImagePanel *m_pBackFolderImage;
+ ImagePanel *m_pEncodedImage;
+ EditablePanel *m_pMainContainer;
+
+ CQuestStatusPanel *m_pEncodedStatus;
+ CQuestStatusPanel *m_pInactiveStatus;
+ CQuestStatusPanel *m_pReadyToTurnInStatus;
+ Label *m_pFlavorText;
+ Label *m_pObjectiveExplanationLabel;
+ Label *m_pExpirationLabel;
+ EditablePanel *m_pTurnInContainer;
+ EditablePanel *m_pTurnInDimmer;
+ Button *m_pTurnInButton;
+ EditablePanel *m_pTurnInSpinnerContainer;
+ CExButton *m_pTitleButton;
+ EditablePanel *m_pIdentifyDimmer;
+ EditablePanel *m_pIdentifyContainer;
+ CExButton *m_pIdentifyButton;
+ ImagePanel *m_pPhotoStatic;
+ ImagePanel *m_pAcceptedImage;
+ Label *m_pTurningInLabel;
+ class CExScrollingEditablePanel *m_pFlavorScrollingContainer;
+ CExButton *m_pFindServerButton;
+
+ // loaners
+ EditablePanel *m_pLoanerContainerPanel;
+ CExButton *m_pRequestLoanerItemsButton;
+ CExButton *m_pEquipLoanerItemsButton;
+ CItemModelPanel *m_pLoanerItemModelPanel[2];
+
+ CExButton *m_pDiscardButton;
+
+
+ int m_nPaperXPos;
+ int m_nPaperYPos;
+ int m_nPaperXShakePos;
+ int m_nPaperYShakePos;
+ bool m_bHasAllControls;
+ CUtlString m_strItemTrackerResFile;
+ CUtlString m_strQuickPlayMap;
+
+ CUtlString m_strMatchmakingGroupName;
+ CUtlString m_strMatchmakingCategoryName;
+ CUtlString m_strMatchmakingMapName;
+
+ // Sound effects
+ CUtlString m_strExpandSound;
+ CUtlString m_strCollapseSound;
+ CUtlString m_strTurnInSound;
+ CUtlString m_strTurnInSuccessSound;
+ CUtlString m_strDecodeSound;
+
+ // Animation
+ CUtlString m_strReset;
+ CUtlString m_strAnimExpand;
+ CUtlString m_strAnimCollapse;
+ CUtlString m_strTurningIn;
+ CUtlString m_strHighlightOn;
+ CUtlString m_strHighlightOff;
+
+ class CItemTrackerPanel *m_pItemTrackerPanel;
+
+ CScrollableQuestList *m_pQuestList;
+
+ RealTimeCountdownTimer m_StateTimer;
+ KeyValues *m_pKVItemTracker;
+
+ struct FolderPair_t
+ {
+ CUtlString m_strFront;
+ CUtlString m_strBack;
+ };
+ CUtlVector< FolderPair_t > m_vecFoldersImages;
+
+ CUtlString m_strEncodedText;
+ CUtlString m_strExpireText;
+ const char *m_pszCompleteSound;
+ bool m_bCollapsed;
+
+ KeyValues *m_pKVCipherStrings;
+
+ CPanelAnimationVarAliasType( int, m_iFrontPaperHideHeight, "front_paper_hide_height", "1000", "proportional_int" ); // Default to a large value so it wont be visible
+ CPanelAnimationVarAliasType( int, m_iUnidentifiedHeight, "unidentified_height", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iObjectiveInset, "objective_inset", "200", "proportional_int" );
+ //CPanelAnimationVarAliasType( int, m_iScrollingContainerHeight, "scrolling_container_height", "200", "proportional_int" );
+
+ enum EDecodeStyle
+ {
+ DECODE_STYLE_CYPHER = 0,
+ DECODE_STYLE_PANEL_FADE,
+ };
+ CPanelAnimationVarAliasType( EDecodeStyle, m_eDecodeStyle, "decode_style", "0", "int" );
+};
+
+#endif // QUEST_ITEM_PANEL_H
diff --git a/game/client/tf/vgui/quest_log_panel.cpp b/game/client/tf/vgui/quest_log_panel.cpp
new file mode 100644
index 0000000..e65c7b0
--- /dev/null
+++ b/game/client/tf/vgui/quest_log_panel.cpp
@@ -0,0 +1,1182 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "quest_log_panel.h"
+#include "ienginevgui.h"
+#include "c_tf_gamestats.h"
+#include "store/store_panel.h"
+#include "econ/econ_ui.h"
+#include "clientmode_tf.h"
+#include "tf_hud_mainmenuoverride.h"
+#include "vgui_int.h"
+#include "IGameUIFuncs.h" // for key bindings
+#include <vgui_controls/AnimationController.h>
+#include "tf_item_inventory.h"
+#include "vgui/IInput.h"
+#include "item_ad_panel.h"
+#include "vgui_controls/ProgressBar.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
+
+static CItemModelPanelToolTip* g_spItemTooltip = NULL;
+CQuestTooltip* g_spTextTooltip = NULL;
+
+CQuestLogPanel *GetQuestLog()
+{
+ CQuestLogPanel *pQuestLogPanel = (CQuestLogPanel*)gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG );
+ return pQuestLogPanel;
+}
+
+//-------------------------------------------------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CScrollableQuestList::CScrollableQuestList( vgui::Panel *parent, const char *pszPanelName )
+ : EditablePanel( parent, pszPanelName )
+ , m_pCompletingPanel( NULL )
+ , m_bQuestsLayoutDirty( false )
+ , m_pszNoQuests( NULL )
+ , m_pszNeedAPass( NULL )
+ , m_pszNotPossible( NULL )
+{
+ m_pContainer = new EditablePanel( this, "Container" );
+ m_vecQuestItemPanels.SetSize( 2 );
+
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ m_vecQuestItemPanels[ i ] = new CQuestItemPanel( m_pContainer, "QuestItemPanel", NULL, this );
+ }
+}
+
+CScrollableQuestList::~CScrollableQuestList()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ const char *pszResFile = "Resource/UI/econ/ScrollableQuestList.res";
+
+ // Check if the operation wants to override our default res file
+ const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
+ FOR_EACH_MAP_FAST( mapOperations, i )
+ {
+ CEconOperationDefinition* pCurrentOperation = mapOperations[i];
+ // Take the first active operation's res file that's different than default
+ if ( pCurrentOperation->IsActive() && pCurrentOperation->GetQuestListOverrideResFile() )
+ {
+ // Use the first found for now
+ pszResFile = pCurrentOperation->GetQuestListOverrideResFile();
+ break;
+ }
+ }
+
+ LoadControlSettings( pszResFile );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::ApplySettings( KeyValues *inResourceData )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ BaseClass::ApplySettings( inResourceData );
+
+ m_pszNoQuests = inResourceData->GetString( "no_quests", "#QuestLog_NoQuests" );
+ m_pszNeedAPass = inResourceData->GetString( "need_a_pass", "#QuestLog_NeedPassForContracts" );
+ m_pszNotPossible = inResourceData->GetString( "not_possible", "#QuestLog_NoContractsPossible" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::PerformLayout( void )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ BaseClass::PerformLayout();
+
+ m_pContainer->InvalidateLayout( true );
+
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ m_vecQuestItemPanels[ i ]->InvalidateLayout( true, true );
+ m_vecQuestItemPanels[ i ]->SetZPos( 1 + i );
+ }
+
+ PositionQuestItemPanels();
+
+ UpdateEmptyMessage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::OnThink()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ if ( m_bQuestsLayoutDirty )
+ {
+ m_bQuestsLayoutDirty = false;
+
+ PositionQuestItemPanels();
+ }
+
+ // Conditionally turn mouse input on/off based on where the mouse is
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ m_vecQuestItemPanels[i]->SetMouseInputEnabled( m_vecQuestItemPanels[i]->IsCursorOverMainContainer() );
+ }
+}
+
+void CScrollableQuestList::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "deselect_all" ) )
+ {
+ SetSelected( NULL, false );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+int QuestSort_AcquiredTime( CQuestItemPanel* const* p1, CQuestItemPanel* const* p2 )
+{
+ if ( !(*p1)->GetItem() )
+ return -1;
+
+ if ( !(*p2)->GetItem() )
+ return 1;
+
+ // Newest items first
+ return (*p1)->GetItem()->GetID() - (*p2)->GetItem()->GetID();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::PositionQuestItemPanels()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ // We dont do anything when a quest is completing
+ if ( m_pCompletingPanel != NULL )
+ return;
+
+ int nVisible = 0;
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ CQuestItemPanel* pPanel = m_vecQuestItemPanels[i];
+ if ( pPanel )
+ {
+ pPanel->SetVisible( pPanel->GetItem() );
+
+ if ( pPanel->GetItem() )
+ {
+ ++nVisible;
+ }
+ }
+ }
+
+ CExLabel *pLabel = FindControl<CExLabel>( "EmptyLabel", true );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( nVisible == 0 );
+ }
+
+ // Check for a selected panel
+ const CQuestItemPanel* pSelected = NULL;
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ if ( m_vecQuestItemPanels[i]->IsSelected() )
+ {
+ Assert( pSelected == NULL );
+ pSelected = m_vecQuestItemPanels[i];
+ }
+ }
+
+ struct FolderCommands_t
+ {
+ const char* m_pszSelected;
+ const char* m_pszOtherIsSelected;
+ const char* m_pszNoneSelected;
+ };
+
+ const FolderCommands_t folderCommands[] = { { "QuestItem_Back_Selected", "QuestItem_Back_OtherSelected", "QuestItem_Back_NoneSelected" }
+ , { "QuestItem_Front_Selected", "QuestItem_Front_OtherSelected", "QuestItem_Front_NoneSelected" } };
+
+ // Update the positions
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ // This is the selected panel
+ if ( pSelected == m_vecQuestItemPanels[ i ] )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszSelected );
+ }
+ else if ( pSelected ) // Some other panel is selected
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszOtherIsSelected );
+ }
+ else // No panel is selected
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszNoneSelected );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::SetSelected( CQuestItemPanel *pItem, bool bImmediately )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ m_vecQuestItemPanels[ i ]->SetSelected( m_vecQuestItemPanels[ i ] == pItem, bImmediately );
+ }
+
+ PositionQuestItemPanels();
+}
+
+bool DoesLootlistDropQuests( const CEconLootListDefinition* pLootList )
+{
+ FOR_EACH_VEC( pLootList->GetLootListContents(), j )
+ {
+ const CEconLootListDefinition::drop_item_t& item = pLootList->GetLootListContents()[j];
+ // 0 and greater means item. Less than 0 means nested lootlist
+ if( item.m_iItemOrLootlistDef >= 0 )
+ {
+ const GameItemDefinition_t* pItemDef = assert_cast<const GameItemDefinition_t *>( GetItemSchema()->GetItemDefinition( item.m_iItemOrLootlistDef ) );
+ if( pItemDef )
+ {
+ return pItemDef->GetQuestDef();
+ }
+ }
+ else
+ {
+ // Get the nested lootlist
+ int iLLIndex = (item.m_iItemOrLootlistDef * -1) - 1;
+ const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex );
+ Assert( pNestedLootList );
+ if ( !pNestedLootList )
+ continue;
+
+ // Dig through all of this lootlist's entries
+ return DoesLootlistDropQuests( pNestedLootList );
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update what message we show when we have no quests
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::UpdateEmptyMessage()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ CCyclingAdContainerPanel* pPassStoreAd = FindControl< CCyclingAdContainerPanel >( "ItemAd", true );
+ if ( !pPassStoreAd )
+ return;
+
+ pPassStoreAd->SetVisible( false );
+ SetDialogVariable( "noquests", "" );
+
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ // Case 1 above
+ if ( m_vecQuestItemPanels[ i ]->GetItem() )
+ return;
+ }
+
+ // By default, there's no operations going on, and there's no ad to show
+ const char *pszNoQuestsText = m_pszNotPossible;
+
+ // Find any active quest-dropping operations
+ const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
+ FOR_EACH_MAP_FAST( mapOperations, iOperation )
+ {
+ CEconOperationDefinition *pOperation = mapOperations[ iOperation ];
+ const CSchemaLootListDefHandle pOperationLootlist( pOperation->GetOperationLootlist() );
+ // Must still be dropping, and be dropping quests
+ if ( CRTime::RTime32TimeCur() < pOperation->GetStopGivingToPlayerDate() && pOperationLootlist && DoesLootlistDropQuests( pOperationLootlist ) )
+ {
+ // If there's a required item and a gateway item
+ if ( pOperation->GetRequiredItemDefIndex() != INVALID_ITEM_DEF_INDEX && pOperation->GetGatewayItemDefIndex() != INVALID_ITEM_DEF_INDEX )
+ {
+ // And the user doesn't have the required item
+ if ( TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pOperation->GetRequiredItemDefIndex() ) == NULL )
+ {
+ // The user needs to get the item
+ pszNoQuestsText = m_pszNeedAPass;
+
+ bool bStoreIsReady = EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser();
+ bool bGatewayItemInStore = false;
+ // Check if the gateway item is in the Mann Co Store
+ if ( bStoreIsReady )
+ {
+ bGatewayItemInStore = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pOperation->GetGatewayItemDefIndex() ) != NULL;
+ }
+
+ CEconItemDefinition* pGatewayItemDef = GetItemSchema()->GetItemDefinition( pOperation->GetGatewayItemDefIndex() );
+ Assert( pGatewayItemDef );
+ if ( !pGatewayItemDef )
+ return;
+
+ // Cook up KVs for this item ad
+ KeyValuesAD pKVItemAd( "items" ); // The panel will copy these
+ KeyValues* pKVItem = pKVItemAd->CreateNewKey();
+ pKVItem->SetName( "0" );
+ pKVItem->SetString( "item", pGatewayItemDef->GetDefinitionName() );
+ pKVItem->SetInt( "show_market", bGatewayItemInStore ? 0 : 1 );
+
+ pPassStoreAd->SetVisible( true );
+ pPassStoreAd->SetItemKVs( pKVItemAd );
+
+ // This is the most important thing to communicate. Don't let other operations stomp it.
+ break;
+ }
+ }
+
+ pszNoQuestsText = m_pszNoQuests;
+ // Don't break. Give more important operations a chance to present
+ }
+ }
+
+ SetDialogVariable( "noquests", g_pVGuiLocalize->Find( pszNoQuestsText ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::PopulateQuestLists()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ // We dont do anything when a quest is completing
+ if ( m_pCompletingPanel != NULL )
+ return;
+
+ DirtyQuestLayout();
+
+ CUtlVector< CEconItemView * > vecQuestItems;
+ TFInventoryManager()->GetAllQuestItems( &vecQuestItems );
+
+ CUtlVector< CQuestItemPanel* > vecAvailablePanels;
+
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ bool bFound = false;
+ if ( m_vecQuestItemPanels[ i ]->GetItem() )
+ {
+ FOR_EACH_VEC_BACK( vecQuestItems, j )
+ {
+ // See if the item in the panel is still in the list of items we own
+ if ( m_vecQuestItemPanels[ i ]->GetItem()->GetOriginalID() == vecQuestItems[ j ]->GetOriginalID() )
+ {
+ // Refresh it
+ m_vecQuestItemPanels[ i ]->InvalidateLayout();
+ vecQuestItems.Remove( j );
+ bFound = true;
+ break;
+ }
+ }
+ }
+
+ // Didn't find it. Clear it out
+ if ( !bFound )
+ {
+ if ( m_vecQuestItemPanels[ i ]->GetItem() )
+ {
+ m_vecQuestItemPanels[ i ]->SetItem( NULL );
+ }
+
+ if ( m_vecQuestItemPanels[ i ]->IsSelected() )
+ {
+ SetSelected( m_vecQuestItemPanels[ i ], false );
+ }
+
+ vecAvailablePanels.AddToTail( m_vecQuestItemPanels[ i ] );
+ }
+ }
+
+ for ( int i = 0 ; i < vecQuestItems.Count(); ++i )
+ {
+ CEconItemView *pItem = vecQuestItems[i];
+
+ if ( i < vecAvailablePanels.Count() )
+ {
+ vecAvailablePanels[ i ]->SetItem( pItem );
+ }
+ else if ( i >= m_vecQuestItemPanels.Count() )
+ {
+ Assert( !"Ran out of quest panels!" );
+ }
+ }
+
+ // Sort the panels to make sure they're in order
+ m_vecQuestItemPanels.Sort( &QuestSort_AcquiredTime );
+
+ // Sorting is done manually
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ m_vecQuestItemPanels[ i ]->SetZPos( i + 1 );
+ }
+
+ PositionQuestItemPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CScrollableQuestList::QuestCompletedResponse()
+{
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ m_vecQuestItemPanels[i]->QuestCompletedResponse();
+ }
+
+// PopulateQuestLists();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if any quest item panels are in the passed in state
+//-----------------------------------------------------------------------------
+bool CScrollableQuestList::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const
+{
+ FOR_EACH_VEC( m_vecQuestItemPanels, i )
+ {
+ if ( m_vecQuestItemPanels[i]->GetState() == eState )
+ return true;
+ }
+
+ return false;
+}
+
+//-------------------------------------------------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestLogPanel::CQuestLogPanel( IViewPort *pViewPort )
+ : EditablePanel( NULL, PANEL_QUEST_LOG )
+ , m_bWaitingForComplete( false )
+ , m_pQuestList( NULL )
+ , m_bInventoryDirty( true )
+ , m_iQuestLogKey( BUTTON_CODE_INVALID )
+{
+ if (g_pVGuiLocalize)
+ {
+ g_pVGuiLocalize->AddFile( "resource/tf_quests_%language%.txt" );
+ }
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "inventory_updated" );
+ ListenForGameEvent( "gameui_hidden" );
+ ListenForGameEvent( "gc_connected" );
+
+ // Create the item model panel tooltip
+ m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" );
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+
+ // Create the text tooltip
+ m_pToolTip = new CQuestTooltip( this );
+ m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" );
+ m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
+ m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
+// m_pToolTipEmbeddedPanel->MakePopup();
+ m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel );
+ m_pToolTip->SetTooltipDelay( 0 );
+
+ EditablePanel *pMainContainer = new EditablePanel( this, "MainContainer" );
+ m_pQuestList = new CScrollableQuestList( pMainContainer, "QuestList" );
+
+ m_pProgressPanel = new EditablePanel( this, "ProgressPanel" );
+
+ m_pDebugButton = new CExButton( pMainContainer, "Options", "Options", this, "open_debug_menu" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Look into the moused-over panel and take "tiptext" from its dialog
+// variables and set it as our own.
+//-----------------------------------------------------------------------------
+void CQuestTooltip::ShowTooltip( Panel *pCurrentPanel )
+{
+ EditablePanel* pEditableCurrentPanel = dynamic_cast< EditablePanel* >( pCurrentPanel );
+ if ( pEditableCurrentPanel )
+ {
+ KeyValues* pKVVariables = pEditableCurrentPanel->GetDialogVariables();
+ const wchar_t *pwszTipText = pKVVariables->GetWString( "tiptext", L"" );
+ m_pEmbeddedPanel->SetDialogVariable( "tiptext", pwszTipText );
+ }
+
+ BaseClass::ShowTooltip( pCurrentPanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Position ourselves down and to the right as far as posible
+//-----------------------------------------------------------------------------
+void CQuestTooltip::PositionWindow( Panel *pTipPanel )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ int iTipW, iTipH;
+ pTipPanel->GetSize( iTipW, iTipH );
+
+ int cursorX, cursorY;
+ vgui::input()->GetCursorPos(cursorX, cursorY);
+
+ int px, py, wide, tall;
+ ipanel()->GetAbsPos( m_pEmbeddedPanel->GetParent()->GetVPanel(), px, py );
+ m_pEmbeddedPanel->GetParent()->GetSize(wide, tall);
+
+ if ( !m_pEmbeddedPanel->IsPopup() )
+ {
+ // Move the cursor into our parent space
+ cursorX -= px;
+ cursorY -= py;
+ }
+
+ // Dangle as far down and as far right as possible
+ int nXPos = cursorX - Max( 0, ( ( iTipW + cursorX ) - wide ) );
+ int nYPos = ( cursorY + 20 )- Max( 0, ( ( iTipH + cursorY + 20 ) - tall ) ) ;
+
+ pTipPanel->SetPos( nXPos, nYPos );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestLogPanel::~CQuestLogPanel()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::AttachToGameUI( void )
+{
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "quest_log_panel" );
+
+ if ( GetClientModeTFNormal()->GameUI() )
+ {
+ GetClientModeTFNormal()->GameUI()->SetMainMenuOverride( GetVPanel() );
+ }
+
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+ SetCursor(dc_arrow);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CQuestLogPanel::GetName( void )
+{
+ return PANEL_QUEST_LOG;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ const char *pszResFile = "Resource/UI/econ/QuestLogPanel.res";
+
+ // Check if the operation wants to override our default res file
+ const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
+ FOR_EACH_MAP_FAST( mapOperations, i )
+ {
+ CEconOperationDefinition* pOperation = mapOperations[i];
+ if ( pOperation->IsActive() && pOperation->IsCampaign() )
+ {
+ // Use the first found for now
+ if ( pOperation->GetQuestLogOverrideResFile() )
+ {
+ pszResFile = pOperation->GetQuestLogOverrideResFile();
+ }
+ break;
+ }
+ }
+
+ LoadControlSettings( pszResFile );
+
+ g_spItemTooltip = m_pMouseOverTooltip;
+ g_spTextTooltip = m_pToolTip;
+
+ // The outer dim / close button
+ {
+ Button* pButton = FindControl<Button>( "OutsideCloseButton" );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( this );
+ pButton->SetPaintBackgroundEnabled( false );
+ }
+ }
+
+ m_pQuestList->InvalidateLayout( false, true );
+
+ Assert( m_pQuestList );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( GetUniverse() != k_EUniversePublic )
+ {
+ int x, y;
+ m_pDebugButton->GetParent()->GetPos( x, y );
+ int w, h;
+ m_pDebugButton->GetParent()->GetSize( w, h );
+ m_pDebugButton->SizeToContents();
+ m_pDebugButton->SetVisible( true );
+ m_pDebugButton->SetPos( x + w - m_pDebugButton->GetWide() - 60, y + 15 );
+ m_pDebugButton->SetZPos( 1000 );
+ }
+ else
+ {
+ m_pDebugButton->SetVisible( false );
+ }
+
+ UpdateQuestsItemPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::OnCommand( const char *pCommand )
+{
+ if ( FStrEq( pCommand, "close" ) )
+ {
+ if ( enginevgui->IsGameUIVisible() )
+ {
+ ShowPanel( false );
+ }
+ else
+ {
+ IViewPortPanel *pQuestLog = ( gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG ) );
+ if ( pQuestLog )
+ {
+ gViewPortInterface->ShowPanel( pQuestLog, false );
+ }
+ }
+ }
+ else if ( Q_stricmp( "open_debug_menu", pCommand ) == 0 )
+ {
+ if ( GetUniverse() == k_EUniverseBeta || GetUniverse() == k_EUniverseDev )
+ {
+ const char *pszContextMenuBorder = "NotificationDefault";
+ const char *pszContextMenuFont = "HudFontMediumSecondary";
+
+ Menu *pContextMenu = new Menu( this, "ContextMenu" );
+ pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+
+ MenuBuilder contextMenuBuilder( pContextMenu, this );
+
+ const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
+ FOR_EACH_MAP_FAST( mapOperations, iOperation )
+ {
+ bool bHasAnyQuests = false;
+ Menu* pOperationSubMenu = NULL;
+
+ CEconOperationDefinition *pOperation = mapOperations[ iOperation ];
+ const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pOperation->GetOperationLootlist() );
+ if ( pLootListDef )
+ {
+ auto& vecContents = pLootListDef->GetLootListContents();
+ FOR_EACH_VEC( vecContents, i )
+ {
+ if ( vecContents[i].m_iItemOrLootlistDef > 0 )
+ {
+ const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinition( vecContents[i].m_iItemOrLootlistDef );
+ if ( pItemDef && pItemDef->GetQuestDef() )
+ {
+ if ( !bHasAnyQuests )
+ {
+ bHasAnyQuests = true;
+
+ pOperationSubMenu = new Menu( this, "OperationSubMenu" );
+ pOperationSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pOperationSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+ contextMenuBuilder.AddCascadingMenuItem( pOperation->GetName(), pOperationSubMenu, "operations" );
+ }
+
+ pOperationSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this );
+ }
+ }
+ }
+ }
+ }
+
+ bool bHasAnyQuests = false;
+ Menu* pAllSubMenu = NULL;
+
+ FOR_EACH_MAP_FAST( GetItemSchema()->GetItemDefinitionMap(), i )
+ {
+ const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinitionMap()[ i ];
+ if ( pItemDef->GetQuestDef() )
+ {
+ if ( !bHasAnyQuests )
+ {
+ bHasAnyQuests = true;
+
+ pAllSubMenu = new Menu( this, "AllSubMenu" );
+ pAllSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pAllSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+ contextMenuBuilder.AddCascadingMenuItem( "all", pAllSubMenu, "all" );
+ }
+
+ pAllSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this );
+ }
+ }
+
+ // Position to the cursor's position
+ int nX, nY;
+ g_pVGuiInput->GetCursorPosition( nX, nY );
+ pContextMenu->SetPos( nX - 1, nY - 1 );
+
+ pContextMenu->SetVisible(true);
+ pContextMenu->AddActionSignalTarget(this);
+ }
+ }
+ else if ( Q_strnicmp( "give", pCommand, 4 ) == 0 )
+ {
+ if ( GetUniverse() != k_EUniversePublic )
+ {
+ if ( !steamapicontext || !steamapicontext->SteamUser() )
+ {
+ Msg("Not connected to Steam.\n");
+ return;
+ }
+
+ CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID();
+ if ( !steamIDForPlayer.IsValid() )
+ {
+ Msg("Failed to find a valid steamID for the local player.\n");
+ return;
+ }
+
+ const char* pszItemToGive = pCommand + 4;
+ Msg( "Sending request to generate '%s' for Local Player (%llu)\n", pszItemToGive, steamIDForPlayer.ConvertToUint64() );
+
+ CItemSelectionCriteria criteria;
+
+ GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest );
+ msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() );
+
+ criteria.SetIgnoreEnabledFlag( true );
+ if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemToGive, true ) ||
+ !criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) )
+ {
+ Msg( "Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" );
+ return;
+ }
+ GCClientSystem()->BSendMessage( msg );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::FireGameEvent( IGameEvent *event )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ // Listen for inventory updates in case our item gets changed while the user
+ // is looking at us. We want to re-do our entire layout since a quest might
+ // have been equipped / destroyed / completed and we need to re-categorize
+ // all the user's quests.
+ if ( FStrEq( event->GetName(), "inventory_updated" ) && !m_bWaitingForComplete )
+ {
+ m_bInventoryDirty = true;
+
+ if ( IsVisible() )
+ {
+ if ( m_pQuestList )
+ {
+ m_pQuestList->PopulateQuestLists();
+ m_pQuestList->UpdateEmptyMessage();
+ }
+
+ UpdateQuestsItemPanels();
+ }
+ }
+ else if ( FStrEq( event->GetName(), "gameui_hidden" ) )
+ {
+ ShowPanel( false );
+ return;
+ }
+ else if ( FStrEq( event->GetName(), "gc_connected" ) )
+ {
+ m_bInventoryDirty = true;
+ InvalidateLayout( false, true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::ShowPanel( bool bShow )
+{
+ // Snag this so we know what to listen for
+ m_iQuestLogKey = gameuifuncs->GetButtonCodeForBind( "show_quest_log" );
+
+ if ( m_pQuestList && bShow )
+ {
+ m_pQuestList->SetSelected( NULL, true );
+ m_pQuestList->DirtyQuestLayout();
+ }
+
+ SetVisible( bShow );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::OnKeyCodePressed( KeyCode code )
+{
+ if ( code == m_iQuestLogKey || code == STEAMCONTROLLER_B )
+ {
+ ShowPanel( false );
+ return;
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::OnKeyCodeTyped( KeyCode code )
+{
+ if ( code == KEY_ESCAPE )
+ {
+ if ( IsVisible() )
+ {
+ SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::SetVisible( bool bState )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ // Default to showing the active quests upon opening
+ if ( bState == true )
+ {
+ UpdateQuestsItemPanels();
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "questlog_opened" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+
+ if ( enginevgui->IsGameUIVisible() )
+ {
+ AttachToGameUI();
+ }
+ else
+ {
+ ipanel()->SetParent( GetVPanel(), VGui_GetClientDLLRootPanel() );
+
+ MakePopup( false, true );
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+ MoveToFront();
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" );
+ vgui::surface()->PlaySound( "ui/panel_open.wav" );
+ }
+ else if ( IsVisible() )
+ {
+ // Detach from the GameUI when we hide
+ IViewPortPanel *pMMOverride = gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE );
+ if ( pMMOverride )
+ {
+ ((CHudMainMenuOverride*)pMMOverride)->AttachToGameUI();
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" );
+ vgui::surface()->PlaySound( "ui/panel_close.wav" );
+ }
+
+ BaseClass::SetVisible( bState );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::QuestCompletedResponse()
+{
+ m_bWaitingForComplete = false;
+ m_bInventoryDirty = true;
+
+ if ( m_pQuestList )
+ m_pQuestList->QuestCompletedResponse();
+
+ //UpdateQuestsItemPanels();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::UpdateQuestsItemPanels()
+{
+ UpdateBadgeProgressPanels();
+
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ if ( m_bInventoryDirty )
+ {
+ m_pQuestList->QuestCompletedResponse();
+
+ if ( m_pQuestList )
+ {
+ m_pQuestList->PopulateQuestLists();
+ m_pQuestList->UpdateEmptyMessage();
+ }
+ }
+
+ m_bInventoryDirty = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::MarkQuestsDirty()
+{
+ m_bInventoryDirty = true;
+
+ if ( IsVisible() )
+ {
+ UpdateQuestsItemPanels();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if any quest item panels are in the passed in state
+//-----------------------------------------------------------------------------
+bool CQuestLogPanel::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const
+{
+ return m_pQuestList->AnyQuestItemPanelsInState( eState );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::OnCompleteQuest( void )
+{
+ m_bWaitingForComplete = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestLogPanel::UpdateBadgeProgressPanels()
+{
+ CEconOperationDefinition *pCurrentOperation = NULL;
+ const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
+ FOR_EACH_MAP_FAST( mapOperations, i )
+ {
+ CEconOperationDefinition* pOperation = mapOperations[i];
+ if ( pOperation->IsActive() && pOperation->IsCampaign() )
+ {
+ pCurrentOperation = pOperation;
+ break;
+ }
+ }
+
+ if ( pCurrentOperation )
+ {
+ CEconItemView *pCoin = TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pCurrentOperation->GetRequiredItemDefIndex() );
+ if ( pCoin )
+ {
+ m_pProgressPanel->SetVisible( true );
+ CItemModelPanel *pCoinPanel = m_pProgressPanel->FindControl< CItemModelPanel >( "CoinModelPanel" );
+ if ( pCoinPanel )
+ {
+ pCoinPanel->SetItem( pCoin );
+ }
+
+ uint32 nNumCompletedContracts = 0;
+ {
+ uint32 nCompletedContractsTemp = 0;
+ GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsCompleted, nCompletedContractsTemp );
+ nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp );
+ // Halloween has it's own thing for whatever reason
+ GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenContractsCompleted, nCompletedContractsTemp );
+ nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp );
+ Assert( pCurrentOperation->GetMaxDropCount() > 0 );
+ }
+
+ uint32 nNumContractPoints = 0;
+ {
+ uint32 nContractsPointsTemp = 0;
+ GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsPoints, nContractsPointsTemp );
+ nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints );
+ // Halloween has it's own thing for whatever reason
+ GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenSouls, nContractsPointsTemp );
+ nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints );
+ }
+
+ EditablePanel *pBadgeContainer = m_pProgressPanel->FindControl< EditablePanel >( "BadgeMeterContainer" );
+ if ( pBadgeContainer )
+ {
+ ContinuousProgressBar *pBadgeProgressBar = pBadgeContainer->FindControl< ContinuousProgressBar >( "BadgeProgressMeter" );
+ if ( pBadgeProgressBar )
+ {
+ const char *pszLevelingDataName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( kKillEaterEvent_HalloweenSouls );
+ Assert( pszLevelingDataName );
+
+ const CUtlVector<CItemLevelingDefinition> *pLevelingData = GetItemSchema()->GetItemLevelingData( pszLevelingDataName );
+ Assert( pLevelingData );
+
+ int nRequiredPointsToNextRank = 0;
+ int nRequiredPointsToCurrentRank = 0;
+ const char *pszCurrentLevelName = NULL;
+ FOR_EACH_VEC( (*pLevelingData), i )
+ {
+ pszCurrentLevelName = (*pLevelingData)[i].GetNameLocalizationKey();
+
+ const uint32 nRank = (*pLevelingData)[i].GetRequiredScore();
+ if ( nNumContractPoints < nRank )
+ {
+ nRequiredPointsToNextRank = nRank;
+ break;
+ }
+ else
+ {
+ nRequiredPointsToCurrentRank = nRank;
+ }
+ }
+
+ // if no next level, just use max points
+ if ( nRequiredPointsToNextRank == 0 )
+ {
+ // assuming each contract's worth 130 (100 point + 30 bonus)
+ nRequiredPointsToNextRank = pCurrentOperation->GetMaxDropCount() * 130;
+ }
+
+ float flProgress = (float)( nNumContractPoints - nRequiredPointsToCurrentRank ) / (float)( nRequiredPointsToNextRank - nRequiredPointsToCurrentRank );
+ pBadgeProgressBar->SetProgress( flProgress );
+
+ CExLabel *pLabel = m_pProgressPanel->FindControl< CExLabel >( "BadgeProgressLabel" );
+ if ( pLabel )
+ {
+ pLabel->SetText( CConstructLocalizedString( g_pVGuiLocalize->Find( "QuestLog_BadgeProgress" ), g_pVGuiLocalize->Find( pszCurrentLevelName ) ) );
+ }
+
+ CExLabel *pScoreLabel = pBadgeContainer->FindControl< CExLabel >( "BadgeProgressMeterText" );
+ if ( pScoreLabel )
+ {
+ pScoreLabel->SetText( CFmtStr( "%d/%d", nNumContractPoints, nRequiredPointsToNextRank ) );
+ }
+ }
+ }
+
+ EditablePanel *pContractContainer = m_pProgressPanel->FindControl< EditablePanel >( "ContractMeterContainer" );
+ if ( pContractContainer )
+ {
+ ContinuousProgressBar *pContractProgressBar = pContractContainer->FindControl< ContinuousProgressBar >( "ContractsCompletedProgressMeter" );
+ if ( pContractProgressBar )
+ {
+ pContractProgressBar->SetProgress( (float)nNumCompletedContracts / (float)pCurrentOperation->GetMaxDropCount() );
+
+ CExLabel *pLabel = pContractContainer->FindControl< CExLabel >( "ContractsCompletedProgressMeterText" );
+ if ( pLabel )
+ {
+ pLabel->SetText( CFmtStr( "%d", nNumCompletedContracts ) );
+ }
+ }
+ }
+ }
+ else
+ {
+ m_pProgressPanel->SetVisible( false );
+ }
+ }
+ else
+ {
+ m_pProgressPanel->SetVisible( false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler for when a quest has been completed
+//-----------------------------------------------------------------------------
+class CGCCompleteQuestCompleteResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCCompleteQuestCompleteResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ itemid_t nNewToolID = 0;
+ if( !msg.BReadUint64Data( &nNewToolID ) )
+ return true;
+
+ CQuestLogPanel *pQuestLog = GetQuestLog();
+ if ( pQuestLog )
+ {
+ pQuestLog->QuestCompletedResponse();
+ }
+
+ return true;
+ }
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCCompleteQuestCompleteResponse, "CGCCompleteQuestCompleteResponse", k_EMsgGCQuestCompleted, GCSDK::k_EServerTypeGCClient );
+
+
+#ifdef STAGING_ONLY
+static void cc_tf_quest_log_reload()
+{
+ CQuestLogPanel *pQuestLog = GetQuestLog();
+ if ( pQuestLog )
+ {
+ pQuestLog->MarkQuestsDirty();
+ pQuestLog->InvalidateLayout( true, true );
+ gViewPortInterface->ShowPanel( pQuestLog, true );
+ }
+}
+ConCommand tf_quest_log_reload( "tf_quest_log_reload", cc_tf_quest_log_reload );
+#endif
diff --git a/game/client/tf/vgui/quest_log_panel.h b/game/client/tf/vgui/quest_log_panel.h
new file mode 100644
index 0000000..1c8fa39
--- /dev/null
+++ b/game/client/tf/vgui/quest_log_panel.h
@@ -0,0 +1,132 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef QUEST_LOG_PANEL_H
+#define QUEST_LOG_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include <game/client/iviewport.h>
+#include "quest_item_panel.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Creates the quest log if it doesnt exists, and gives you a pointer to it
+//-----------------------------------------------------------------------------
+class CQuestLogPanel *GetQuestLog();
+
+//-----------------------------------------------------------------------------
+// A scrollable list of quest items
+//-----------------------------------------------------------------------------
+class CScrollableQuestList : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CScrollableQuestList, EditablePanel );
+public:
+ CScrollableQuestList( Panel *parent, const char *pszPanelName );
+ virtual ~CScrollableQuestList();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ void DirtyQuestLayout() { m_bQuestsLayoutDirty = true; }
+ void PopulateQuestLists();
+ void QuestCompletedResponse();
+ bool AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const;
+ void PositionQuestItemPanels();
+
+ void SetSelected( CQuestItemPanel *pItem, bool bImmediately );
+ void SetCompletingPanel( const CQuestItemPanel *pItem ) { m_pCompletingPanel = pItem; }
+ const CQuestItemPanel *GetCompletingPanel() const { return m_pCompletingPanel; }
+ void UpdateEmptyMessage();
+
+protected:
+
+ bool m_bQuestsLayoutDirty;
+ EditablePanel *m_pContainer;
+ CUtlVector< CQuestItemPanel* > m_vecQuestItemPanels;
+ CQuestItemPanel* m_spCompletingPanel;
+ const CQuestItemPanel* m_pCompletingPanel;
+
+ CUtlString m_pszNoQuests;
+ CUtlString m_pszNeedAPass;
+ CUtlString m_pszNotPossible;
+
+ CPanelAnimationVarAliasType( int, m_iEntryStep, "entry_step", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iEntryStartingX, "entry_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iEntryStartingY, "entry_y", "0", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// The default quest log panel
+//-----------------------------------------------------------------------------
+class CQuestLogPanel : public EditablePanel, public IViewPortPanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CQuestLogPanel, EditablePanel );
+public:
+ CQuestLogPanel( IViewPort *pViewPort );
+ virtual ~CQuestLogPanel();
+
+ void AttachToGameUI();
+ virtual const char *GetName( void ) OVERRIDE;
+ virtual void SetData( KeyValues *data ) OVERRIDE {}
+ virtual void Reset() OVERRIDE { Update(); SetVisible( true ); }
+ virtual void Update() OVERRIDE { return; }
+ virtual bool NeedsUpdate( void ) OVERRIDE { return false; }
+ virtual bool HasInputElements( void ) OVERRIDE { return true; }
+ virtual void ShowPanel( bool bShow ) OVERRIDE;
+
+ // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui
+ vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); }
+ virtual bool IsVisible() OVERRIDE { return BaseClass::IsVisible(); }
+ virtual void SetParent( vgui::VPANEL parent ) OVERRIDE { BaseClass::SetParent( parent ); }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnCommand( const char *pCommand ) OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+ virtual void SetVisible( bool bState ) OVERRIDE;
+ virtual void OnKeyCodePressed( KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodeTyped(KeyCode code) OVERRIDE;
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_NONE; }
+
+ void QuestCompletedResponse();
+ void UpdateQuestsItemPanels();
+ void MarkQuestsDirty();
+
+ void UpdateBadgeProgressPanels();
+
+ bool AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const;
+
+ MESSAGE_FUNC( OnCompleteQuest, "CompleteQuest" );
+
+private:
+
+ CScrollableQuestList *m_pQuestList;
+
+ class CItemModelPanel *m_pMouseOverItemPanel;
+ class CItemModelPanelToolTip *m_pMouseOverTooltip;
+
+ EditablePanel *m_pProgressPanel;
+
+ class CQuestTooltip *m_pToolTip;
+ EditablePanel *m_pToolTipEmbeddedPanel;
+
+ ButtonCode_t m_iQuestLogKey;
+ bool m_bWaitingForComplete;
+ bool m_bInventoryDirty;
+
+ Button *m_pDebugButton;
+};
+
+#endif // QUEST_LOG_PANEL_H
diff --git a/game/client/tf/vgui/quest_notification_panel.cpp b/game/client/tf/vgui/quest_notification_panel.cpp
new file mode 100644
index 0000000..c3c03aa
--- /dev/null
+++ b/game/client/tf/vgui/quest_notification_panel.cpp
@@ -0,0 +1,533 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "quest_notification_panel.h"
+#include "vgui/ISurface.h"
+#include "ienginevgui.h"
+#include "hudelement.h"
+#include "iclientmode.h"
+#include "basemodel_panel.h"
+#include "tf_item_inventory.h"
+#include "quest_log_panel.h"
+#include "econ_controls.h"
+#include "c_tf_player.h"
+#include <vgui_controls/AnimationController.h>
+#include "engine/IEngineSound.h"
+#include "econ_item_system.h"
+#include "tf_hud_item_progress_tracker.h"
+#include "tf_spectatorgui.h"
+#include "econ_quests.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+ConVar tf_quest_notification_line_delay( "tf_quest_notification_line_delay", "1.2", FCVAR_ARCHIVE );
+
+extern ISoundEmitterSystemBase *soundemitterbase;
+CQuestNotificationPanel *g_pQuestNotificationPanel = NULL;
+
+DECLARE_HUDELEMENT( CQuestNotificationPanel );
+
+CQuestNotification::CQuestNotification( CEconItem *pItem )
+ : m_hItem( pItem )
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CQuestNotification::Present( CQuestNotificationPanel* pNotificationPanel )
+{
+ m_timerDialog.Start( tf_quest_notification_line_delay.GetFloat() );
+
+ return 0.f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestNotification_Speaking::CQuestNotification_Speaking( CEconItem *pItem )
+ : CQuestNotification( pItem )
+{
+ m_pszSoundToSpeak = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CQuestNotification_Speaking::Present( CQuestNotificationPanel* pNotificationPanel )
+{
+ CQuestNotification::Present( pNotificationPanel );
+
+ if ( m_hItem )
+ {
+ C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( !pPlayer )
+ return 0.f;
+
+ CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer );
+ if ( !pTFPlayer )
+ return 0.f;
+
+ const GameItemDefinition_t *pItemDef = m_hItem->GetItemDefinition();
+ // Get our quest theme
+ const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme();
+ if ( pTheme )
+ {
+ // Get the sound we need to speak
+ m_pszSoundToSpeak = GetSoundEntry( pTheme, pTFPlayer->GetPlayerClass()->GetClassIndex() );
+ float flPresentTime = 0.f;
+ if ( m_pszSoundToSpeak )
+ {
+ flPresentTime = enginesound->GetSoundDuration( m_pszSoundToSpeak ) + m_timerDialog.GetCountdownDuration() + 1.f;
+ m_timerShow.Start( enginesound->GetSoundDuration( m_pszSoundToSpeak ) + m_timerDialog.GetCountdownDuration() + 1.f );
+ }
+
+ return flPresentTime;
+ }
+ }
+
+ return 0.f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestNotification_Speaking::Update( CQuestNotificationPanel* pNotificationPanel )
+{
+ if ( m_timerDialog.IsElapsed() && m_timerDialog.HasStarted() && m_hItem )
+ {
+ m_timerDialog.Invalidate();
+
+ // Play it!
+ if ( m_pszSoundToSpeak )
+ {
+ vgui::surface()->PlaySound( m_pszSoundToSpeak );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestNotification_Speaking::IsDone() const
+{
+ return m_timerShow.IsElapsed() && m_timerShow.HasStarted();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CQuestNotification_NewQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex )
+{
+ return pTheme->GetGiveSoundForClass( nClassIndex );
+}
+
+bool CQuestNotification_NewQuest::ShouldPresent() const
+{
+ C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( !pPlayer )
+ return false;
+
+ CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer );
+ if ( !pTFPlayer )
+ return false;
+
+ IViewPortPanel* pSpecGuiPanel = gViewPortInterface->FindPanelByName( PANEL_SPECGUI );
+ if ( !pTFPlayer->IsAlive() )
+ {
+ if ( !pSpecGuiPanel || !pSpecGuiPanel->IsVisible() )
+ return false;
+ }
+ else
+ {
+ // Local player is in a spawn room
+ if ( pTFPlayer->m_Shared.GetRespawnTouchCount() <= 0 )
+ return false;
+ }
+
+ return true;
+}
+
+CQuestNotification_CompletedQuest::CQuestNotification_CompletedQuest( CEconItem *pItem )
+ : CQuestNotification_Speaking( pItem )
+{
+ const char *pszSoundName = UTIL_GetRandomSoundFromEntry( "Quest.StatusTickComplete" );
+ m_PresentTimer.Start( enginesound->GetSoundDuration( pszSoundName ) - 2.f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CQuestNotification_CompletedQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex )
+{
+ return pTheme->GetCompleteSoundForClass( nClassIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestNotification_CompletedQuest::ShouldPresent() const
+{
+ return m_PresentTimer.IsElapsed() && m_PresentTimer.HasStarted();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CQuestNotification_FullyCompletedQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex )
+{
+ return pTheme->GetFullyCompleteSoundForClass( nClassIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestNotificationPanel::CQuestNotificationPanel( const char *pszElementName )
+ : CHudElement( pszElementName )
+ , EditablePanel( NULL, "QuestNotificationPanel" )
+ , m_flTimeSinceLastShown( 0.f )
+ , m_bIsPresenting( false )
+ , m_mapNotifiedItemIDs( DefLessFunc( itemid_t ) )
+ , m_bInitialized( false )
+ , m_pMainContainer( NULL )
+{
+ Panel *pParent = g_pClientMode->GetViewport();
+ SetParent( pParent );
+
+ g_pQuestNotificationPanel = this;
+
+ ListenForGameEvent( "player_death" );
+ ListenForGameEvent( "inventory_updated" );
+ ListenForGameEvent( "player_initial_spawn" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CQuestNotificationPanel::~CQuestNotificationPanel()
+{}
+
+
+
+
+void CQuestNotificationPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // Default, load pauling
+ LoadControlSettings( "Resource/UI/econ/QuestNotificationPanel_Pauling_standard.res" );
+
+ m_pMainContainer = FindControl< EditablePanel >( "MainContainer", true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestNotificationPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ CExLabel* pNewQuestLabel = FindControl< CExLabel >( "NewQuestText", true );
+ if ( pNewQuestLabel )
+ {
+ const wchar_t *pszText = NULL;
+ const char *pszTextKey = "#QuestNotification_Accept";
+ if ( pszTextKey )
+ {
+ pszText = g_pVGuiLocalize->Find( pszTextKey );
+ }
+ if ( pszText )
+ {
+ wchar_t wzFinal[512] = L"";
+ UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) );
+ pNewQuestLabel->SetText( wzFinal );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestNotificationPanel::FireGameEvent( IGameEvent * event )
+{
+ const char *pszName = event->GetName();
+
+ if ( FStrEq( pszName, "inventory_updated" ) || FStrEq( pszName, "player_death" ) )
+ {
+ CheckForNotificationOpportunities();
+ }
+ else if ( FStrEq( pszName, "player_initial_spawn" ) )
+ {
+ CTFPlayer *pNewPlayer = ToTFPlayer( UTIL_PlayerByIndex( event->GetInt( "index" ) ) );
+ if ( pNewPlayer == C_BasePlayer::GetLocalPlayer() )
+ {
+ // Reset every round
+ m_mapNotifiedItemIDs.Purge();
+ m_vecNotifications.PurgeAndDeleteElements();
+ m_timerNotificationCooldown.Start( 0 );
+ m_bInitialized = false;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestNotificationPanel::Reset()
+{
+ CheckForNotificationOpportunities();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestNotificationPanel::CheckForNotificationOpportunities()
+{
+ // Suppress making new notifications while in competitive play
+ if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
+ return;
+
+ FOR_EACH_VEC_BACK( m_vecNotifications, i )
+ {
+ // Clean up old entires for items that are now gone
+ if ( m_vecNotifications[i]->GetItemHandle() == NULL )
+ {
+ delete m_vecNotifications[i];
+ m_vecNotifications.Remove( i );
+ }
+ }
+
+ CPlayerInventory *pInv = InventoryManager()->GetLocalInventory();
+ Assert( pInv );
+ if ( pInv )
+ {
+ for ( int i = 0 ; i < pInv->GetItemCount(); ++i )
+ {
+ CEconItemView *pItem = pInv->GetItem( i );
+
+ // Check if this is a quest at all
+ if ( pItem->GetItemDefinition()->GetQuestDef() == NULL )
+ continue;
+
+ CQuestNotification* pNotification = NULL;
+ if ( IsUnacknowledged( pItem->GetInventoryPosition() ) )
+ {
+ pNotification = new CQuestNotification_NewQuest( pItem->GetSOCData() );
+ }
+ else if ( IsQuestItemFullyCompleted( pItem ) ) // Fully completed
+ {
+ pNotification = new CQuestNotification_FullyCompletedQuest( pItem->GetSOCData() );
+ }
+ else if ( IsQuestItemReadyToTurnIn( pItem ) ) // Ready to turn in
+ {
+ pNotification = new CQuestNotification_CompletedQuest( pItem->GetSOCData() );
+ }
+ else
+ {
+ // Clean up any pending notifications for normal quests
+ FOR_EACH_VEC_BACK( m_vecNotifications, j )
+ {
+ if ( m_vecNotifications[j]->GetItemHandle() == pItem->GetSOCData() )
+ {
+ delete m_vecNotifications[j];
+ m_vecNotifications.Remove( j );
+ }
+ }
+ }
+
+ if ( pNotification && !AddNotificationForItem( pItem, pNotification ) )
+ {
+ delete pNotification;
+ pNotification = NULL;
+ }
+ }
+
+ m_bInitialized = pInv->GetOwner().IsValid();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestNotificationPanel::AddNotificationForItem( const CEconItemView *pItem, CQuestNotification* pNotification )
+{
+ bool bTypeAlreadyInQueue = false;
+ // Check if there's already a notification of this type
+ FOR_EACH_VEC_BACK( m_vecNotifications, i )
+ {
+ // There's already a quest of this type in queue, no need to add another
+ if ( m_vecNotifications[i]->GetType() == pNotification->GetType() )
+ {
+ bTypeAlreadyInQueue = true;
+ break;
+ }
+ }
+
+ // Find the notified bits
+ auto idx = m_mapNotifiedItemIDs.Find( pItem->GetItemID() );
+ if ( idx == m_mapNotifiedItemIDs.InvalidIndex() )
+ {
+ // Create if missing
+ idx = m_mapNotifiedItemIDs.Insert( pItem->GetItemID() );
+ m_mapNotifiedItemIDs[ idx ].SetSize( CQuestNotification::NUM_NOTIFICATION_TYPES );
+ FOR_EACH_VEC( m_mapNotifiedItemIDs[ idx ], i )
+ {
+ m_mapNotifiedItemIDs[ idx ][ i ] = 0.f;
+ }
+ }
+
+ // Check if we've already done a notification for this type recently
+ if ( Plat_FloatTime() < m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] || m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] == NEVER_REPEAT )
+ {
+ return false;
+ }
+
+ bool bNotificationUsed = false;
+ // Don't play completed notifications unless they happen mid-play
+ if ( !bTypeAlreadyInQueue && ( m_bInitialized || pNotification->GetType() == CQuestNotification::NOTIFICATION_TYPE_NEW_QUEST ) )
+ {
+ // Add notification
+ m_vecNotifications.AddToTail( pNotification );
+ bNotificationUsed = true;
+ }
+
+ // Mark that we've created a notification of this type for this item
+ m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] = pNotification->GetReplayTime() == NEVER_REPEAT ? NEVER_REPEAT : Plat_FloatTime() + pNotification->GetReplayTime();
+
+ return bNotificationUsed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestNotificationPanel::ShouldDraw()
+{
+ C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( !pPlayer )
+ return false;
+
+ CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer );
+ if ( !pTFPlayer )
+ return false;
+
+ // Not selected a class, so they haven't joined in
+ if ( pTFPlayer->IsPlayerClass( 0 ) )
+ return false;
+
+ if ( !CHudElement::ShouldDraw() )
+ return false;
+
+ if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestNotificationPanel::OnThink()
+{
+ if ( !ShouldDraw() )
+ return;
+
+ bool bHasStarted = m_animTimer.HasStarted();
+ float flShowProgress = bHasStarted ? 1.f : 0.f;
+ const float flTransitionTime = 0.5f;
+
+ Update();
+
+ if ( bHasStarted )
+ {
+ // Transitions
+ if ( m_animTimer.GetElapsedTime() < flTransitionTime )
+ {
+ flShowProgress = Bias( m_animTimer.GetElapsedTime() / flTransitionTime, 0.75f );
+ }
+ else if ( ( m_animTimer.GetRemainingTime() + 1.f ) < flTransitionTime )
+ {
+ flShowProgress = Bias( Max( 0.0f, m_animTimer.GetRemainingTime() + 1.f ) / flTransitionTime, 0.25f );
+ }
+ }
+
+ // Move the main container around
+ if ( m_pMainContainer )
+ {
+ int nY = g_pSpectatorGUI && g_pSpectatorGUI->IsVisible() ? g_pSpectatorGUI->GetTopBarHeight() : 0;
+
+ float flXPos = RemapValClamped( flShowProgress, 0.f, 1.f, 0.f, m_pMainContainer->GetWide() + XRES( 4 ) );
+ m_pMainContainer->SetPos( GetWide() - (int)flXPos, nY );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CQuestNotificationPanel::ShouldPresent()
+{
+ if ( !m_timerNotificationCooldown.IsElapsed() )
+ return false;
+
+ // We need notifications!
+ if ( m_vecNotifications.IsEmpty() )
+ return false;
+
+ // It's been a few seconds since we were last shown
+ if ( ( Plat_FloatTime() - m_flTimeSinceLastShown ) < 1.5f )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CQuestNotificationPanel::Update()
+{
+ bool bAllowedToShow = ShouldPresent();
+
+ if ( bAllowedToShow && !m_bIsPresenting )
+ {
+ if ( m_vecNotifications.Head()->ShouldPresent() )
+ {
+ float flPresentTime = m_vecNotifications.Head()->Present( this );
+ m_animTimer.Start( flPresentTime );
+
+ m_timerHoldUp.Start( 3.f );
+
+ // Notification sound
+ vgui::surface()->PlaySound( "ui/quest_alert.wav" );
+ m_bIsPresenting = true;
+ }
+ }
+ else if ( !bAllowedToShow && m_bIsPresenting && m_timerHoldUp.IsElapsed() )
+ {
+ m_flTimeSinceLastShown = Plat_FloatTime();
+ // Play the slide-out animation
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "QuestNotification_Hide" );
+ m_bIsPresenting = false;
+ }
+ else if ( m_bIsPresenting ) // We are presenting a notification
+ {
+ if ( m_vecNotifications.Count() )
+ {
+ m_vecNotifications.Head()->Update( this );
+ // Check if the notification is done
+ if ( m_vecNotifications.Head()->IsDone() )
+ {
+ // Start our cooldown
+ m_timerNotificationCooldown.Start( 1.f );
+ // We're done with this notification
+ delete m_vecNotifications.Head();
+ m_vecNotifications.Remove( 0 );
+ }
+ }
+ }
+}
diff --git a/game/client/tf/vgui/quest_notification_panel.h b/game/client/tf/vgui/quest_notification_panel.h
new file mode 100644
index 0000000..1fcdb38
--- /dev/null
+++ b/game/client/tf/vgui/quest_notification_panel.h
@@ -0,0 +1,180 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef QUEST_NOTIFICATION_PANEL_H
+#define QUEST_NOTIFICATION_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui/VGUI.h>
+#include "hudelement.h"
+#include "vgui_controls/EditablePanel.h"
+#include <../common/GameUI/cvarslider.h>
+#include "vgui_controls/CheckButton.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "econ_item_inventory.h"
+
+using namespace vgui;
+#define NEVER_REPEAT -1.f
+
+class CQuestNotificationPanel;
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+class CQuestNotification
+{
+public:
+ CQuestNotification( CEconItem *pItem );
+
+ enum ENotificationType_t
+ {
+ NOTIFICATION_TYPE_NEW_QUEST = 0,
+ NOTIFICATION_TYPE_COMPLETED,
+ NOTIFICATION_TYPE_FULLY_COMPLETED,
+
+ NUM_NOTIFICATION_TYPES
+ };
+
+ virtual ~CQuestNotification() {}
+
+ virtual float Present( CQuestNotificationPanel* pNotificationPanel );
+ virtual void Update( CQuestNotificationPanel* pNotificationPanel ) = 0;
+ virtual bool IsDone() const = 0;
+ virtual bool ShouldPresent() const = 0;
+ virtual ENotificationType_t GetType() const = 0;
+ virtual float GetReplayTime() const = 0;
+
+ CEconItemHandle& GetItemHandle() { return m_hItem; }
+
+protected:
+ CEconItemHandle m_hItem;
+ RealTimeCountdownTimer m_timerDialog;
+ RealTimeCountdownTimer m_timerShow;
+};
+
+//-----------------------------------------------------------------------------
+// Notifications where we'll speak
+//-----------------------------------------------------------------------------
+class CQuestNotification_Speaking : public CQuestNotification
+{
+public:
+
+ CQuestNotification_Speaking( CEconItem *pItem );
+ virtual ~CQuestNotification_Speaking() {}
+
+ virtual float Present( CQuestNotificationPanel* pNotificationPanel ) OVERRIDE;
+ virtual void Update( CQuestNotificationPanel* pNotificationPanel ) OVERRIDE;
+ virtual bool IsDone() const OVERRIDE;
+
+protected:
+ virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) = 0;
+
+ const char *m_pszSoundToSpeak;
+};
+
+//-----------------------------------------------------------------------------
+// New quest notification
+//-----------------------------------------------------------------------------
+class CQuestNotification_NewQuest : public CQuestNotification_Speaking
+{
+ DECLARE_CLASS_SIMPLE( CQuestNotification_NewQuest, CQuestNotification_Speaking );
+public:
+ CQuestNotification_NewQuest( CEconItem *pItem )
+ : CQuestNotification_Speaking( pItem )
+ {}
+
+ virtual ~CQuestNotification_NewQuest() {}
+
+ virtual bool ShouldPresent() const OVERRIDE;
+ ENotificationType_t GetType() const { return NOTIFICATION_TYPE_NEW_QUEST; }
+ virtual float GetReplayTime() const { return 300.f; }
+ static float k_flReplayTime;
+
+protected:
+ virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) OVERRIDE;
+
+ static CUtlVector< itemid_t > m_vecNotifiedItemIDs;
+};
+
+//-----------------------------------------------------------------------------
+// Quest complete notification
+//-----------------------------------------------------------------------------
+class CQuestNotification_CompletedQuest : public CQuestNotification_Speaking
+{
+ DECLARE_CLASS_SIMPLE( CQuestNotification_CompletedQuest, CQuestNotification_Speaking );
+public:
+ CQuestNotification_CompletedQuest( CEconItem *pItem );
+
+ virtual ~CQuestNotification_CompletedQuest() {}
+
+ virtual bool ShouldPresent() const;
+ ENotificationType_t GetType() const { return NOTIFICATION_TYPE_COMPLETED; }
+ virtual float GetReplayTime() const { return NEVER_REPEAT; }
+
+protected:
+ virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) OVERRIDE;
+
+ RealTimeCountdownTimer m_PresentTimer;
+};
+
+class CQuestNotification_FullyCompletedQuest : public CQuestNotification_CompletedQuest
+{
+ DECLARE_CLASS_SIMPLE( CQuestNotification_FullyCompletedQuest, CQuestNotification_CompletedQuest );
+public:
+ CQuestNotification_FullyCompletedQuest( CEconItem *pItem ) : CQuestNotification_CompletedQuest( pItem )
+ {
+ }
+
+ virtual ~CQuestNotification_FullyCompletedQuest() {}
+
+ ENotificationType_t GetType() const { return NOTIFICATION_TYPE_FULLY_COMPLETED; }
+protected:
+ virtual const char *GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex ) OVERRIDE;
+};
+
+//-----------------------------------------------------------------------------
+// The quest notification panel where a character tells the user about quest state
+//-----------------------------------------------------------------------------
+class CQuestNotificationPanel : public CHudElement, public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CQuestNotificationPanel, EditablePanel );
+public:
+ CQuestNotificationPanel( const char *pszElementName );
+ virtual ~CQuestNotificationPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void FireGameEvent( IGameEvent * event ) OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual bool ShouldDraw() OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+private:
+
+ bool ShouldPresent();
+
+ void Update();
+ void CheckForNotificationOpportunities();
+
+ bool AddNotificationForItem( const CEconItemView *pItem, CQuestNotification* pNotification );
+ void SetCharacterImage( const char *pszImageName );
+
+ CUtlVector< CQuestNotification* > m_vecNotifications;
+
+ float m_flTimeSinceLastShown;
+ bool m_bIsPresenting;
+ RealTimeCountdownTimer m_timerHoldUp;
+ RealTimeCountdownTimer m_timerNotificationCooldown;
+ RealTimeCountdownTimer m_animTimer;
+ EditablePanel *m_pMainContainer;
+ bool m_bInitialized;
+
+ CUtlMap< itemid_t, CCopyableUtlVector< float > > m_mapNotifiedItemIDs;
+};
+
+#endif // QUEST_NOTIFICATION_PANEL_H
diff --git a/game/client/tf/vgui/report_player_dialog.cpp b/game/client/tf/vgui/report_player_dialog.cpp
new file mode 100644
index 0000000..62eb9b8
--- /dev/null
+++ b/game/client/tf/vgui/report_player_dialog.cpp
@@ -0,0 +1,305 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "report_player_dialog.h"
+#include "gc_clientsystem.h"
+#include "ienginevgui.h"
+
+using namespace vgui;
+
+// in seconds
+static const float MIN_REPORT_INTERVAL = 300.f;
+
+struct ReportedPlayer_t
+{
+ CSteamID steamID;
+ float flReportedTime;
+};
+CUtlVector< ReportedPlayer_t > vecReportedPlayers;
+
+bool CanReportPlayer( CSteamID steamID, bool bVerbose )
+{
+ bool bCanReport = true;
+ for (int i = 0; i < vecReportedPlayers.Count(); ++i)
+ {
+ if ( vecReportedPlayers[i].steamID == steamID )
+ {
+ float flTimeSinceLastReported = gpGlobals->curtime - vecReportedPlayers[i].flReportedTime;
+ bCanReport = flTimeSinceLastReported >= MIN_REPORT_INTERVAL;
+ if ( !bCanReport && bVerbose )
+ {
+ float flCooldownTime = MIN_REPORT_INTERVAL - flTimeSinceLastReported;
+ ConMsg( "Already reported this player. You can report this player again in %.2f seconds\n", flCooldownTime );
+ }
+ break;
+ }
+ }
+
+ return bCanReport;
+}
+
+bool ReportPlayerAccount( CSteamID steamID, int nReason )
+{
+ if ( !steamID.IsValid() )
+ {
+ Warning( "Reporting an invalid steam ID\n" );
+ return false;
+ }
+
+ if ( !CanReportPlayer( steamID, true ) )
+ {
+ return false;
+ }
+
+ if ( nReason <= CMsgGC_ReportPlayer_EReason_kReason_INVALID || nReason >= CMsgGC_ReportPlayer_EReason_kReason_COUNT )
+ {
+ Assert( !"Invalid report reason" );
+ return false;
+ }
+
+ GCSDK::CProtoBufMsg< CMsgGC_ReportPlayer > msg( k_EMsgGC_ReportPlayer );
+ msg.Body().set_account_id_target( steamID.GetAccountID() );
+ msg.Body().set_reason( (CMsgGC_ReportPlayer_EReason)nReason );
+ GCClientSystem()->BSendMessage( msg );
+ ConMsg( "Report sent. Thank you.\n" );
+
+ ReportedPlayer_t reportedPlayer;
+ reportedPlayer.steamID = steamID;
+ reportedPlayer.flReportedTime = gpGlobals->curtime;
+ vecReportedPlayers.AddToTail( reportedPlayer );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CReportPlayerDialog::CReportPlayerDialog( vgui::Panel *parent ) : BaseClass( parent, "ReportPlayerDialog" )
+{
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFile("resource/SourceScheme.res", "Client");
+ SetScheme(scheme);
+
+ SetSize( 320, 270 );
+ SetTitle( "#GameUI_ReportPlayerCaps", true );
+
+ m_pReportButton = new Button( this, "ReportButton", "" );
+ m_pPlayerList = new ListPanel( this, "PlayerList" );
+ m_pPlayerList->AddColumnHeader( 0, "Name", "#GameUI_PlayerName", 180 );
+ m_pPlayerList->AddColumnHeader( 1, "Properties", "#GameUI_Properties", 80 );
+ m_pPlayerList->SetEmptyListText( "#GameUI_NoOtherPlayersInGame" );
+ m_pReasonBox = new ComboBox( this, "ReasonBox", 5, false );
+
+ LoadControlSettings( "Resource/ReportPlayerDialog.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CReportPlayerDialog::~CReportPlayerDialog()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReportPlayerDialog::Activate()
+{
+ BaseClass::Activate();
+
+ m_pPlayerList->DeleteAllItems();
+
+ static EUniverse universe = steamapicontext->SteamUtils()->GetConnectedUniverse();
+
+ for ( int i = 1; i <= engine->GetMaxClients(); i++ )
+ {
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( i, &pi ) )
+ continue;
+
+ // no need to add local player
+ if ( engine->GetLocalPlayer() == i )
+ continue;
+
+ // Already reported
+ CSteamID steamID( pi.friendsID, universe, k_EAccountTypeIndividual );
+ if ( !CanReportPlayer( steamID, false ) )
+ {
+ continue;
+ }
+
+ char szPlayerIndex[32];
+ Q_snprintf( szPlayerIndex, sizeof( szPlayerIndex ), "%d", i );
+
+ KeyValues *pData = new KeyValues( szPlayerIndex );
+ pData->SetString( "Name", pi.name );
+ pData->SetInt( "index", i );
+ m_pPlayerList->AddItem( pData, 0, false, false );
+ }
+
+ m_pReasonBox->RemoveAll();
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ SetDialogVariable( "combo_label", g_pVGuiLocalize->Find( "#GameUI_ReportPlayerReason" ) );
+ pKeyValues->SetInt( "reason", 0 );
+ m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Choose" ), pKeyValues );
+ pKeyValues->SetInt( "reason", 1 );
+ m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Cheating" ), pKeyValues );
+ pKeyValues->SetInt( "reason", 2 );
+ m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Idle" ), pKeyValues );
+ pKeyValues->SetInt( "reason", 3 );
+ m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Harassment" ), pKeyValues );
+ pKeyValues->SetInt( "reason", 4 );
+ m_pReasonBox->AddItem( g_pVGuiLocalize->Find( "GameUI_ReportPlayer_Griefing" ), pKeyValues );
+ m_pReasonBox->SilentActivateItemByRow( 0 );
+ pKeyValues->deleteThis();
+
+ RefreshPlayerProperties();
+ m_pPlayerList->SetSingleSelectedItem( m_pPlayerList->GetItemIDFromRow( 0 ) );
+ OnItemSelected();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: walks the players and sets their info display in the list
+//-----------------------------------------------------------------------------
+void CReportPlayerDialog::RefreshPlayerProperties()
+{
+ for ( int i = 0; i <= m_pPlayerList->GetItemCount(); i++ )
+ {
+ KeyValues *pData = m_pPlayerList->GetItem( i );
+ if ( !pData )
+ continue;
+
+ int playerIndex = pData->GetInt( "index" );
+
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( playerIndex, &pi ) )
+ {
+ pData->SetString( "properties", "Disconnected" );
+ continue;
+ }
+
+ pData->SetString( "name", pi.name );
+
+ if ( pi.fakeplayer )
+ {
+ pData->SetString( "properties", "CPU Player" );
+ }
+ else
+ {
+ pData->SetString( "properties", "" );
+ }
+ }
+ m_pPlayerList->RereadAllItems();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CReportPlayerDialog::IsValidPlayerSelected()
+{
+ bool bIsValidPlayer = false;
+
+ if ( m_pPlayerList->GetSelectedItemsCount() > 0 )
+ {
+ KeyValues *pData = m_pPlayerList->GetItem( m_pPlayerList->GetSelectedItem( 0 ) );
+ player_info_t pi;
+ bIsValidPlayer = engine->GetPlayerInfo( pData->GetInt( "index" ), &pi );
+#ifdef _DEBUG
+ bIsValidPlayer = bIsValidPlayer && pData->GetInt( "index" ) != engine->GetLocalPlayer();
+#else
+ bIsValidPlayer = bIsValidPlayer && !pi.fakeplayer && pData->GetInt( "index" ) != engine->GetLocalPlayer();
+#endif
+ }
+
+ return bIsValidPlayer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReportPlayerDialog::OnCommand( const char *command )
+{
+ if ( !stricmp( command, "Report" ) )
+ {
+ ReportPlayer();
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReportPlayerDialog::ReportPlayer()
+{
+ for ( int iSelectedItem = 0; iSelectedItem < m_pPlayerList->GetSelectedItemsCount(); iSelectedItem++ )
+ {
+ KeyValues *pPlayerData = m_pPlayerList->GetItem( m_pPlayerList->GetSelectedItem( iSelectedItem ) );
+ if ( !pPlayerData )
+ return;
+
+ Assert( pPlayerData->GetInt( "index" ) );
+
+ // INVALID = 0;
+ // CHEATING = 1;
+ // IDLE = 2;
+ // HARASSMENT = 3;
+ // GRIEFING = 4;
+
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( pPlayerData->GetInt( "index" ), &pi ) )
+ return;
+
+ CSteamID steamID( pi.friendsID, GetUniverse(), k_EAccountTypeIndividual );
+ KeyValues *pReasonData = m_pReasonBox->GetActiveItemUserData();
+ int nReason = ( pReasonData ) ? pReasonData->GetInt( "reason", 0 ) : 0;
+ ReportPlayerAccount( steamID, nReason );
+ Close();
+ return;
+ }
+
+ RefreshPlayerProperties();
+ OnItemSelected();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CReportPlayerDialog::OnItemSelected()
+{
+ RefreshPlayerProperties();
+
+ bool bReportButtonEnabled = IsValidPlayerSelected();
+ if ( !bReportButtonEnabled )
+ {
+ m_pReportButton->SetText( "#GameUI_ReportPlayer" );
+ }
+
+ // Reason selected?
+ KeyValues *pUserData = m_pReasonBox->GetActiveItemUserData();
+ bReportButtonEnabled = bReportButtonEnabled && pUserData && pUserData->GetInt( "reason", 0 ) > 0;
+ m_pReportButton->SetEnabled( bReportButtonEnabled );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CReportPlayerDialog::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast< vgui::Panel* >( data->GetPtr( "panel" ) );
+ vgui::ComboBox *pComboBox = dynamic_cast< vgui::ComboBox* >( pPanel );
+ if ( pComboBox && pComboBox == m_pReasonBox )
+ {
+ bool bReportButtonEnabled = IsValidPlayerSelected();
+ KeyValues *pReasonData = m_pReasonBox->GetActiveItemUserData();
+ bReportButtonEnabled = bReportButtonEnabled && pReasonData && pReasonData->GetInt( "reason", 0 ) > 0;
+ m_pReportButton->SetEnabled( bReportButtonEnabled );
+ }
+}
diff --git a/game/client/tf/vgui/report_player_dialog.h b/game/client/tf/vgui/report_player_dialog.h
new file mode 100644
index 0000000..52b8307
--- /dev/null
+++ b/game/client/tf/vgui/report_player_dialog.h
@@ -0,0 +1,56 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#ifndef REPORT_PLAYER_DIALOG_H
+#define REPORT_PLAYER_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/Frame.h"
+#include "vgui_controls/ListPanel.h"
+#include "vgui_controls/Button.h"
+#include "vgui_controls/ComboBox.h"
+
+class CReportPlayerDialog : public vgui::Frame
+{
+ DECLARE_CLASS_SIMPLE( CReportPlayerDialog, vgui::Frame );
+
+public:
+ CReportPlayerDialog( vgui::Panel *parent );
+ ~CReportPlayerDialog();
+
+ virtual void Activate();
+
+private:
+ MESSAGE_FUNC( OnItemSelected, "ItemSelected" );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+ virtual void OnCommand( const char *command );
+
+ void ReportPlayer();
+ void RefreshPlayerProperties();
+ bool IsValidPlayerSelected();
+
+ void OnKeyCodePressed( vgui::KeyCode code )
+ {
+ if ( code == KEY_XBUTTON_B )
+ {
+ Close();
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+ }
+
+ vgui::ListPanel *m_pPlayerList;
+ vgui::Button *m_pReportButton;
+ vgui::ComboBox *m_pReasonBox;
+};
+
+bool ReportPlayerAccount( CSteamID steamID, int nReason );
+
+#endif // REPORT_PLAYER_DIALOG_H
diff --git a/game/client/tf/vgui/sc_hinticon.cpp b/game/client/tf/vgui/sc_hinticon.cpp
new file mode 100644
index 0000000..703c121
--- /dev/null
+++ b/game/client/tf/vgui/sc_hinticon.cpp
@@ -0,0 +1,70 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "sc_hinticon.h"
+#include <vgui/IVGui.h>
+#include "inputsystem/iinputsystem.h"
+
+using namespace vgui;
+
+DECLARE_BUILD_FACTORY( CSCHintIcon );
+
+//-----------------------------------------------------------------------------
+CSCHintIcon::CSCHintIcon( vgui::Panel *parent, const char* panelName ) :
+ vgui::Label( parent, panelName, L"" )
+ , m_bIsActionMapped( false )
+ , m_actionSetHandle( 0 )
+{
+ m_szActionName[0] = '\0';
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSCHintIcon::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ auto szActionName = inResourceData->GetString( "actionName", "" );
+ Q_strncpy( m_szActionName, szActionName, nMaxActionNameLength );
+
+ auto szActionSet = inResourceData->GetString( "actionSet", nullptr );
+ if ( szActionSet )
+ {
+ m_actionSetHandle = g_pInputSystem->GetActionSetHandle( szActionSet );
+ }
+ else
+ {
+ m_actionSetHandle = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSCHintIcon::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ const wchar_t* iconText = L"";
+ m_bIsActionMapped = false;
+ if ( m_actionSetHandle )
+ {
+ auto origin = g_pInputSystem->GetSteamControllerActionOrigin( m_szActionName, m_actionSetHandle );
+ if ( origin != k_EControllerActionOrigin_None )
+ {
+ iconText = g_pInputSystem->GetSteamControllerFontCharacterForActionOrigin( origin );
+ if ( iconText && iconText[0] )
+ {
+ m_bIsActionMapped = true;
+ }
+ }
+ }
+
+ SetText( iconText );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/sc_hinticon.h b/game/client/tf/vgui/sc_hinticon.h
new file mode 100644
index 0000000..337ece2
--- /dev/null
+++ b/game/client/tf/vgui/sc_hinticon.h
@@ -0,0 +1,39 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Control for displaying a Steam Controller hint icon
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef SC_HINTICON_H
+#define SC_HINTICON_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui/IScheme.h>
+#include <vgui/KeyCode.h>
+#include <KeyValues.h>
+#include <vgui/IVGui.h>
+#include <vgui_controls/Label.h>
+
+class CSCHintIcon : public vgui::Label
+{
+public:
+ DECLARE_CLASS_SIMPLE( CSCHintIcon, vgui::Label );
+
+ CSCHintIcon( vgui::Panel *parent, const char *panelName );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ bool IsActionMapped() const { return m_bIsActionMapped; }
+
+private:
+ bool m_bIsActionMapped;
+ static const int nMaxActionNameLength = 63;
+ char m_szActionName[nMaxActionNameLength+1];
+ ControllerActionSetHandle_t m_actionSetHandle;
+};
+
+#endif // SC_HINTICON_H \ No newline at end of file
diff --git a/game/client/tf/vgui/select_player_dialog.cpp b/game/client/tf/vgui/select_player_dialog.cpp
new file mode 100644
index 0000000..fd8c072
--- /dev/null
+++ b/game/client/tf/vgui/select_player_dialog.cpp
@@ -0,0 +1,446 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextEntry.h"
+#include "select_player_dialog.h"
+#include "tf_controls.h"
+#include "c_playerresource.h"
+#include "ienginevgui.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+int CSelectPlayerDialog::SortPartnerInfoFunc( const partner_info_t *pA, const partner_info_t *pB )
+{
+ return Q_stricmp( pA->m_name.Get(), pB->m_name.Get() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CSelectPlayerDialog::CSelectPlayerDialog( vgui::Panel *parent )
+ : vgui::EditablePanel( parent, "SelectPlayerDialog" )
+ , m_bAllowSameTeam( true )
+ , m_bAllowOutsideServer( true )
+{
+ if ( parent == NULL )
+ {
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+ }
+
+ m_pSelectFromServerButton = NULL;
+ m_pCancelButton = NULL;
+ m_pButtonKV = NULL;
+ m_bReapplyButtonKVs = false;
+
+ for ( int i = 0; i < SPDS_NUM_STATES; i++ )
+ {
+ m_pStatePanels[i] = new vgui::EditablePanel( this, VarArgs("StatePanel%d",i) );
+ }
+
+ m_pPlayerList = new vgui::EditablePanel( this, "PlayerList" );
+ m_pPlayerListScroller = new vgui::ScrollableEditablePanel( this, m_pPlayerList, "PlayerListScroller" );
+
+ m_iCurrentState = SPDS_SELECTING_PLAYER;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CSelectPlayerDialog::~CSelectPlayerDialog( void )
+{
+ if ( m_pButtonKV )
+ {
+ m_pButtonKV->deleteThis();
+ m_pButtonKV = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::Reset( void )
+{
+ m_iCurrentState = SPDS_SELECTING_PLAYER;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "button_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pButtonKV )
+ {
+ m_pButtonKV->deleteThis();
+ }
+ m_pButtonKV = new KeyValues("button_kv");
+ pItemKV->CopySubkeys( m_pButtonKV );
+
+ m_bReapplyButtonKVs = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetResFile() );
+
+ m_pCancelButton = dynamic_cast<CExButton*>( FindChildByName( "CancelButton" ) );
+
+ // Find all the sub buttons, and set their action signals to point to this panel
+ for ( int i = 0; i < SPDS_NUM_STATES; i++ )
+ {
+ int iButton = 0;
+ CExButton *pButton = NULL;
+ do
+ {
+ pButton = dynamic_cast<CExButton*>( m_pStatePanels[i]->FindChildByName( VarArgs("subbutton%d",iButton)) );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( this );
+
+ // The second button on the first state is the server button
+ if ( iButton == 1 )
+ {
+ m_pSelectFromServerButton = pButton;
+ }
+
+ iButton++;
+ }
+ } while (pButton);
+ }
+
+ UpdateState();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // Layout the player list buttons
+ if ( m_pPlayerPanels.Count() )
+ {
+ int iButtonH = m_pPlayerPanels[0]->GetTall() + YRES(2);
+ m_pPlayerList->SetSize( m_pPlayerList->GetWide(), YRES(2) + (iButtonH * m_pPlayerPanels.Count()) );
+
+ // These need to all be layout-complete before we can position the player panels,
+ // because the scrollbar will cause the playerlist entries to move when it lays out.
+ m_pPlayerList->InvalidateLayout( true );
+ m_pPlayerListScroller->InvalidateLayout( true );
+ m_pPlayerListScroller->GetScrollbar()->InvalidateLayout( true );
+
+ for ( int i = 0; i < m_pPlayerPanels.Count(); i++ )
+ {
+ m_pPlayerPanels[i]->SetPos( 0, YRES(2) + (iButtonH * i) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "cancel" ) )
+ {
+ if ( m_iCurrentState != SPDS_SELECTING_PLAYER )
+ {
+ m_iCurrentState = SPDS_SELECTING_PLAYER;
+ UpdateState();
+ return;
+ }
+
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+ if ( GetParent() )
+ {
+ PostMessage( GetParent(), new KeyValues("CancelSelection") );
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "friends" ) )
+ {
+ m_iCurrentState = SPDS_SELECTING_FROM_FRIENDS;
+ UpdateState();
+ return;
+ }
+ else if ( !Q_stricmp( command, "server" ) )
+ {
+ m_iCurrentState = SPDS_SELECTING_FROM_SERVER;
+ UpdateState();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "select_player", 13 ) )
+ {
+ int iPlayer = atoi( command + 13 ) - 1;
+ if ( iPlayer >= 0 && iPlayer < m_PlayerInfoList.Count() )
+ {
+ m_iCurrentState = SPDS_SELECTING_PLAYER;
+ OnCommand( "cancel" );
+ OnSelectPlayer( m_PlayerInfoList[iPlayer].m_steamID );
+ }
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::UpdateState( void )
+{
+ for ( int i = 0; i < SPDS_NUM_STATES; i++ )
+ {
+ if ( !m_pStatePanels[i] )
+ continue;
+
+ m_pStatePanels[i]->SetVisible( m_iCurrentState == i );
+ }
+
+ if ( m_pSelectFromServerButton )
+ {
+ m_pSelectFromServerButton->SetEnabled( engine->IsInGame() );
+ }
+
+ if ( m_iCurrentState == SPDS_SELECTING_PLAYER )
+ {
+ m_pCancelButton->SetText( g_pVGuiLocalize->Find( "#Cancel" ) );
+ }
+ else
+ {
+ m_pCancelButton->SetText( g_pVGuiLocalize->Find( "#TF_Back" ) );
+ }
+
+ switch ( m_iCurrentState )
+ {
+ case SPDS_SELECTING_FROM_FRIENDS:
+ SetupSelectFriends();
+ break;
+ case SPDS_SELECTING_FROM_SERVER:
+ SetupSelectServer( false );
+ break;
+ case SPDS_SELECTING_PLAYER:
+ default:
+ m_pPlayerListScroller->SetVisible( false );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::SetupSelectFriends( void )
+{
+ // @todo optional check to see if friend is on my server
+ if ( m_bAllowOutsideServer == false )
+ {
+ SetupSelectServer( true );
+ return;
+ }
+
+ m_PlayerInfoList.Purge();
+
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ // Get our game info so we can use that to test if our friends are connected to the same game as us
+ FriendGameInfo_t myGameInfo;
+ CSteamID mySteamID = steamapicontext->SteamUser()->GetSteamID();
+ steamapicontext->SteamFriends()->GetFriendGamePlayed( mySteamID, &myGameInfo );
+
+ int iFriends = steamapicontext->SteamFriends()->GetFriendCount( k_EFriendFlagImmediate );
+ for ( int i = 0; i < iFriends; i++ )
+ {
+ CSteamID friendSteamID = steamapicontext->SteamFriends()->GetFriendByIndex( i, k_EFriendFlagImmediate );
+
+ FriendGameInfo_t gameInfo;
+ if ( !AllowOutOfGameFriends() && !steamapicontext->SteamFriends()->GetFriendGamePlayed( friendSteamID, &gameInfo ) )
+ continue;
+
+ // Friends is in-game. Make sure it's TF2.
+ if ( AllowOutOfGameFriends() || (gameInfo.m_gameID.IsValid() && gameInfo.m_gameID == myGameInfo.m_gameID) )
+ {
+ const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( friendSteamID );
+ int idx = m_PlayerInfoList.AddToTail();
+ partner_info_t &info = m_PlayerInfoList[idx];
+ info.m_steamID = friendSteamID;
+ info.m_name = pszName;
+ }
+ }
+ }
+
+ UpdatePlayerList();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::SetupSelectServer( bool bFriendsOnly )
+{
+ m_PlayerInfoList.Purge();
+
+ if ( steamapicontext && steamapicontext->SteamUtils() )
+ {
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ // find all players who are on the local player's team
+ int iLocalPlayerIndex = GetLocalPlayerIndex();
+ if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) )
+ {
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
+ continue;
+ if ( !pi.friendsID )
+ continue;
+
+ CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
+
+ if ( bFriendsOnly )
+ {
+ EFriendRelationship eRelationship = steamapicontext->SteamFriends()->GetFriendRelationship( steamID );
+ if ( eRelationship != k_EFriendRelationshipFriend )
+ {
+ continue;
+ }
+ }
+
+ if ( g_PR->GetTeam( iPlayerIndex ) != TF_TEAM_RED && g_PR->GetTeam( iPlayerIndex ) != TF_TEAM_BLUE )
+ continue;
+
+ if ( m_bAllowSameTeam == false )
+ {
+ if ( GetLocalPlayerTeam() == g_PR->GetTeam( iPlayerIndex ) )
+ {
+ continue;
+ }
+ }
+
+ int idx = m_PlayerInfoList.AddToTail();
+ partner_info_t &info = m_PlayerInfoList[idx];
+ info.m_steamID = steamID;
+ info.m_name = pi.name;
+ }
+ }
+ }
+
+ UpdatePlayerList();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerDialog::UpdatePlayerList( void )
+{
+ vgui::Label *pLabelEmpty = dynamic_cast<vgui::Label*>( m_pStatePanels[m_iCurrentState]->FindChildByName("EmptyPlayerListLabel") );
+ vgui::Label *pLabelQuery = dynamic_cast<vgui::Label*>( m_pStatePanels[m_iCurrentState]->FindChildByName("QueryLabel") );
+
+ // If we have no players in our list, show the no-player label.
+ if ( m_PlayerInfoList.Count() == 0 )
+ {
+ if ( pLabelEmpty )
+ {
+ pLabelEmpty->SetVisible( true );
+ }
+ if ( pLabelQuery )
+ {
+ pLabelQuery->SetVisible( false );
+ }
+ return;
+ }
+
+ // First, reapply any KVs we have to reapply
+ if ( m_bReapplyButtonKVs )
+ {
+ m_bReapplyButtonKVs = false;
+
+ if ( m_pButtonKV )
+ {
+ FOR_EACH_VEC( m_pPlayerPanels, i )
+ {
+ m_pPlayerPanels[i]->ApplySettings( m_pButtonKV );
+ }
+ }
+ }
+
+ // sort by name
+ m_PlayerInfoList.Sort( &SortPartnerInfoFunc );
+
+ // Otherwise, build the player panels from the list of steam IDs
+ for ( int i = 0; i < m_PlayerInfoList.Count(); i++ )
+ {
+ if ( m_pPlayerPanels.Count() <= i )
+ {
+ m_pPlayerPanels.AddToTail();
+ m_pPlayerPanels[i] = new CSelectPlayerTargetPanel( m_pPlayerList, VarArgs("player%d",i) );
+ m_pPlayerPanels[i]->GetButton()->SetCommand( VarArgs("select_player%d",i+1) );
+ m_pPlayerPanels[i]->GetButton()->AddActionSignalTarget( this );
+ m_pPlayerPanels[i]->GetAvatar()->SetShouldDrawFriendIcon( false );
+ m_pPlayerPanels[i]->GetAvatar()->SetMouseInputEnabled( false );
+
+ if ( m_pButtonKV )
+ {
+ m_pPlayerPanels[i]->ApplySettings( m_pButtonKV );
+ m_pPlayerPanels[i]->InvalidateLayout( true );
+ }
+ }
+
+ m_pPlayerPanels[i]->SetInfo( m_PlayerInfoList[i].m_steamID, m_PlayerInfoList[i].m_name );
+ }
+
+ m_pPlayerListScroller->GetScrollbar()->SetAutohideButtons( true );
+ m_pPlayerListScroller->GetScrollbar()->SetValue( 0 );
+
+ // Remove any extra player panels
+ for ( int i = m_pPlayerPanels.Count()-1; i >= m_PlayerInfoList.Count(); i-- )
+ {
+ m_pPlayerPanels[i]->MarkForDeletion();
+ m_pPlayerPanels.Remove(i);
+ }
+
+ if ( pLabelEmpty )
+ {
+ pLabelEmpty->SetVisible( false );
+ }
+ if ( pLabelQuery )
+ {
+ pLabelQuery->SetVisible( true );
+ }
+ m_pPlayerListScroller->SetVisible( true );
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSelectPlayerTargetPanel::SetInfo( const CSteamID &steamID, const char *pszName )
+{
+ if ( !steamapicontext || !steamapicontext->SteamFriends() )
+ return;
+
+ m_pAvatar->SetPlayer( steamID, k_EAvatarSize64x64 );
+
+ m_pButton->SetText( pszName );
+}
+
diff --git a/game/client/tf/vgui/select_player_dialog.h b/game/client/tf/vgui/select_player_dialog.h
new file mode 100644
index 0000000..64ef206
--- /dev/null
+++ b/game/client/tf/vgui/select_player_dialog.h
@@ -0,0 +1,107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef SELECT_PLAYER_DIALOG_H
+#define SELECT_PLAYER_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "tf_controls.h"
+#include "vgui_avatarimage.h"
+
+// Select Player Dialog states
+enum
+{
+ SPDS_SELECTING_PLAYER,
+ SPDS_SELECTING_FROM_FRIENDS,
+ SPDS_SELECTING_FROM_SERVER,
+
+ SPDS_NUM_STATES,
+};
+
+// Button that displays the name & avatar image of a potential target
+class CSelectPlayerTargetPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CSelectPlayerTargetPanel, vgui::EditablePanel );
+public:
+ CSelectPlayerTargetPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name )
+ {
+ m_pAvatar = new CAvatarImagePanel( this, "avatar" );
+ m_pButton = new CExButton( this, "button", "", parent );
+ }
+ ~CSelectPlayerTargetPanel( void )
+ {
+ m_pAvatar->MarkForDeletion();
+ m_pButton->MarkForDeletion();
+ }
+
+ void SetInfo( const CSteamID &steamID, const char *pszName );
+
+ CAvatarImagePanel *GetAvatar( void ) { return m_pAvatar; }
+ CExButton *GetButton( void ) { return m_pButton; }
+
+private:
+ // Embedded panels
+ CAvatarImagePanel *m_pAvatar;
+ CExButton *m_pButton;
+};
+
+//-----------------------------------------------------------------------------
+// A dialog that allows users to select who they want to do something with
+//-----------------------------------------------------------------------------
+class CSelectPlayerDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CSelectPlayerDialog, vgui::EditablePanel );
+public:
+ CSelectPlayerDialog( vgui::Panel *parent );
+ ~CSelectPlayerDialog( void );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+
+ void UpdateState( void );
+ virtual void UpdatePlayerList( void );
+ virtual void Reset( void );
+ virtual void SetupSelectFriends( void );
+ virtual void SetupSelectServer( bool bFriendsOnly );
+
+ virtual bool AllowOutOfGameFriends() { return false; }
+
+ virtual void OnSelectPlayer( const CSteamID &steamID ) = 0;
+
+protected:
+ virtual const char *GetResFile() { return "resource/ui/SelectPlayerDialog.res"; }
+
+ struct partner_info_t
+ {
+ CSteamID m_steamID;
+ CUtlString m_name;
+ };
+
+ static int SortPartnerInfoFunc( const partner_info_t *pA, const partner_info_t *pB );
+
+ vgui::EditablePanel *m_pStatePanels[SPDS_NUM_STATES];
+ int m_iCurrentState;
+ CExButton *m_pSelectFromServerButton;
+ CExButton *m_pCancelButton;
+
+ vgui::EditablePanel *m_pPlayerList;
+ vgui::ScrollableEditablePanel *m_pPlayerListScroller;
+ CUtlVector<partner_info_t> m_PlayerInfoList;
+ CUtlVector<CSelectPlayerTargetPanel*> m_pPlayerPanels;
+ KeyValues *m_pButtonKV;
+ bool m_bReapplyButtonKVs;
+ bool m_bAllowSameTeam;
+ bool m_bAllowOutsideServer;
+};
+
+#endif // SELECT_PLAYER_DIALOG_H
diff --git a/game/client/tf/vgui/softline.cpp b/game/client/tf/vgui/softline.cpp
new file mode 100644
index 0000000..173c983
--- /dev/null
+++ b/game/client/tf/vgui/softline.cpp
@@ -0,0 +1,96 @@
+#include "cbase.h"
+#include "softline.h"
+#include <KeyValues.h>
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+SoftLine::SoftLine(vgui::Panel *parent, const char *panelName, Color col) :
+ vgui::Panel(parent, panelName)
+{
+ m_Color = col;
+ m_iCornerType = 0;
+}
+
+void SoftLine::Paint()
+{
+ if (m_iCornerType == 1)
+ DrawSoftLine(1,GetTall() - 2, GetWide() - 2, 1, m_Color);
+ else
+ DrawSoftLine(1,1, GetWide() - 2, GetTall() - 2, m_Color);
+}
+
+int SoftLine::s_nWhiteTexture = -1;
+
+void SoftLine::DrawSoftLine(float x, float y, float x2, float y2, Color c)
+{
+ vgui::Vertex_t start, end;
+
+ if (s_nWhiteTexture == -1)
+ {
+ s_nWhiteTexture = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile( s_nWhiteTexture, "vgui/white" , true, false);
+ if (s_nWhiteTexture == -1)
+ return;
+ return;
+ }
+
+ // draw main line
+ vgui::surface()->DrawSetTexture(s_nWhiteTexture);
+ vgui::surface()->DrawSetColor(c);
+
+ //vgui::surface()->DrawLine(x,y,x2,y2);
+ start.Init(Vector2D(x,y), Vector2D(0,0));
+ end.Init(Vector2D(x2,y2), Vector2D(1,1));
+ DrawPolygonLine(start, end);
+
+ // draw translucent ones around it to give it some softness
+ vgui::surface()->DrawSetColor(c);
+
+ start.Init(Vector2D(x - 0.50f,y - 0.50f), Vector2D(0,0));
+ end.Init(Vector2D(x2 - 0.50f,y2 - 0.50f), Vector2D(1,1));
+ DrawPolygonLine(start, end);
+
+ start.Init(Vector2D(x + 0.50f,y - 0.50f), Vector2D(0,0));
+ end.Init(Vector2D(x2 + 0.50f,y2 - 0.50f), Vector2D(1,1));
+ DrawPolygonLine(start, end);
+
+ start.Init(Vector2D(x - 0.50f,y + 0.50f), Vector2D(0,0));
+ end.Init(Vector2D(x2 - 0.50f,y2 + 0.50f), Vector2D(1,1));
+ DrawPolygonLine(start, end);
+
+ start.Init(Vector2D(x + 0.50f,y + 0.50f), Vector2D(0,0));
+ end.Init(Vector2D(x2 + 0.50f,y2 + 0.50f), Vector2D(1,1));
+ DrawPolygonLine(start, end);
+}
+
+// draws a line using polygon calls
+void SoftLine::DrawPolygonLine(vgui::Vertex_t start, vgui::Vertex_t end, float width)
+{
+ DrawPolygonLine(start.m_Position.x, start.m_Position.y, end.m_Position.x, end.m_Position.y, width);
+}
+
+void SoftLine::DrawPolygonLine(float x, float y, float x2, float y2, float width)
+{
+ // find long edge
+ Vector2D start(x, y);
+ Vector2D end(x2, y2);
+ Vector2D long_edge = end - start;
+ // normalize and rotate 90 degrees to get our short edge
+ Vector2D short_edge = long_edge;
+ short_edge.NormalizeInPlace();
+ float newx = cos(1.5708f) * short_edge.x - sin(1.5708f) * short_edge.y;
+ float newy = sin(1.5708f) * short_edge.x - cos(1.5708f) * short_edge.y;
+ short_edge.x = newx;
+ short_edge.y = newy;
+ short_edge *= width;
+ vgui::Vertex_t points[4] =
+ {
+ vgui::Vertex_t( Vector2D(x, y) - short_edge * 0.5f, Vector2D(0,0) ),
+ vgui::Vertex_t( Vector2D(x, y) - short_edge * 0.5f + long_edge, Vector2D(1,0) ),
+ vgui::Vertex_t( Vector2D(x, y) + short_edge * 0.5f + long_edge, Vector2D(1,1) ),
+ vgui::Vertex_t( Vector2D(x, y) + short_edge * 0.5f, Vector2D(0,1) )
+ };
+ vgui::surface()->DrawTexturedPolygon(4, points);
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/softline.h b/game/client/tf/vgui/softline.h
new file mode 100644
index 0000000..cbc9ec9
--- /dev/null
+++ b/game/client/tf/vgui/softline.h
@@ -0,0 +1,35 @@
+#ifndef _INCLUDED_SOFT_LINE_H
+#define _INCLUDED_SOFT_LINE_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui/VGUI.h>
+#include <vgui_controls/Panel.h>
+#include <vgui/ISurface.h>
+
+// this is a vgui panel that draws a line between opposite corners
+// the line is softened with translucent lines around it
+
+class SoftLine : public vgui::Panel
+{
+ DECLARE_CLASS_SIMPLE( SoftLine, vgui::Panel );
+public:
+ SoftLine(vgui::Panel *parent, const char *panelName, Color col);
+ virtual void Paint();
+ void DrawSoftLine(float x, float y, float x2, float y2, Color c);
+ void SetCornerType(int i) { m_iCornerType = i; }
+
+ Color m_Color;
+ int m_iCornerType;
+
+ static int s_nWhiteTexture;
+
+ // draws a line between two points using polygon rather than line drawing functions (since line doesn't work sometimes)
+ static void DrawPolygonLine(float x, float y, float x2, float y2, float width=1.0f);
+ static void DrawPolygonLine(vgui::Vertex_t start, vgui::Vertex_t end, float width=1.0f);
+};
+
+
+#endif // _INCLUDED_SOFT_LINE_H \ No newline at end of file
diff --git a/game/client/tf/vgui/store/tf_store.cpp b/game/client/tf/vgui/store/tf_store.cpp
new file mode 100644
index 0000000..d1a827b
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store.cpp
@@ -0,0 +1,7 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+
+#include "cbase.h"
+#include "store/tf_store.h"
diff --git a/game/client/tf/vgui/store/tf_store.h b/game/client/tf/vgui/store/tf_store.h
new file mode 100644
index 0000000..67c83d2
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store.h
@@ -0,0 +1,12 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef TF_STORE_H
+#define TF_STORE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#endif // TF_STORE_H
diff --git a/game/client/tf/vgui/store/tf_store_page_base.cpp b/game/client/tf/vgui/store/tf_store_page_base.cpp
new file mode 100644
index 0000000..ee867e8
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store_page_base.cpp
@@ -0,0 +1,278 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/tf_store_page_base.h"
+//#include "store/v1/tf_store_preview_item.h"
+#include "econ_item_inventory.h"
+#include "store/store_viewcart.h"
+#include "c_tf_freeaccount.h"
+#include "rtime.h"
+#include "econ_ui.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern const char *g_aPlayerClassNames[TF_CLASS_MENU_BUTTONS];
+
+const char *g_szClassFilterStrings[] =
+{
+ "", // Undefined
+ "#Store_Items_Scout",
+ "#Store_Items_Sniper",
+ "#Store_Items_Soldier",
+ "#Store_Items_Demoman",
+ "#Store_Items_Medic",
+ "#Store_Items_HWGuy",
+ "#Store_Items_Pyro",
+ "#Store_Items_Spy",
+ "#Store_Items_Engineer"
+};
+
+DECLARE_BUILD_FACTORY( CStorePreviewClassIcon );
+
+ConVar tf_explanations_store( "tf_explanations_store", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePageBase::CTFStorePageBase(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile ) : CStorePage(parent, pPageData, pPreviewItemResFile)
+{
+ m_flStartExplanationsAt = 0;
+
+ // TF has an option for each class, all class items, all items, and an unowned item option. Let's make sure they all fit.
+ if ( m_pFilterComboBox )
+ {
+ m_pFilterComboBox->SetNumberOfEditLines( 12 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePageBase::OnPageShow( void )
+{
+ BaseClass::OnPageShow();
+
+ // If this is the first time we've opened the store, start the armory explanations
+ if ( !tf_explanations_store.GetBool() && m_pPageData )
+ {
+ m_flStartExplanationsAt = engine->Time() + 0.5;
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePageBase::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "show_explanations" ) )
+ {
+ if ( !m_flStartExplanationsAt )
+ {
+ m_flStartExplanationsAt = engine->Time();
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+ RequestFocus();
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePageBase::GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters )
+{
+ pVecFilters->AddToTail( FILTER_ALL_ITEMS );
+
+ // Add item to unowned filter only if it doesn't belong to these categories.
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pDef->GetDefinitionIndex() );
+ if( !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Tools ) &&
+ !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Maps ) &&
+ !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Bundles ) &&
+ !pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Collections ) )
+ {
+ bool bItemOwned = false;
+ int iCount = InventoryManager()->GetLocalInventory()->GetItemCount();
+ for ( int i = 0; i < iCount; i++ )
+ {
+ if ( InventoryManager()->GetLocalInventory()->GetItem( i )->GetItemDefIndex() == pDef->GetDefinitionIndex() )
+ {
+ bItemOwned = true;
+ break;
+ }
+ }
+
+ if ( !bItemOwned )
+ {
+ pVecFilters->AddToTail( FILTER_UNOWNED_ITEMS );
+ }
+ }
+
+ if ( pDef->CanBeUsedByAllClasses() )
+ pVecFilters->AddToTail( FILTER_ALLCLASS_ITEMS );
+
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( pDef->CanBeUsedByClass( iClass ) )
+ pVecFilters->AddToTail( iClass );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePageBase::OnItemDetails( vgui::Panel *panel )
+{
+ CStoreItemControlsPanel *pControlsPanel = dynamic_cast< CStoreItemControlsPanel * >( panel );
+ if ( pControlsPanel )
+ {
+ const econ_store_entry_t *pEntry = pControlsPanel->GetItem();
+ if ( pEntry )
+ {
+ SelectItemPanel( pControlsPanel->GetItemModelPanel() );
+ PostMessage( EconUI()->GetStorePanel(), new KeyValues("ArmoryOpened", "itemdef", pEntry->GetItemDefinitionIndex() ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePageBase::ShowPreview( int iClass, const econ_store_entry_t* pEntry )
+{
+ if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS )
+ {
+ iClass = TF_CLASS_SCOUT;
+ }
+
+ BaseClass::ShowPreview( iClass, pEntry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePageBase::UpdateFilterComboBox( void )
+{
+ if ( !m_pFilterComboBox )
+ return;
+
+ wchar_t wzLocalized[256];
+ wchar_t wszCount[16];
+
+ m_pFilterComboBox->RemoveAll();
+
+ // All items
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "filter", FILTER_ALL_ITEMS );
+ m_pFilterComboBox->AddItem( "#Store_ClassFilter_None", pKeyValues );
+
+#if NEWFILTER
+ // All classes
+ int nCount = m_pPrimaryFilter->GetCountForFilterItem( FILTER_ALLCLASS_ITEMS );
+ if ( nCount )
+ {
+ pKeyValues->SetInt( "filter", FILTER_ALLCLASS_ITEMS );
+
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nCount );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_ClassFilter_AllClasses" ), 1, wszCount );
+ m_pFilterComboBox->AddItem( wzLocalized, pKeyValues );
+ }
+
+ // Individual classes
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ nCount = m_pPrimaryFilter->GetCountForFilterItem( iClass );
+ if ( !nCount )
+ continue;
+
+ pKeyValues->SetInt( "filter", iClass );
+
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nCount );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( g_szClassFilterStrings[iClass] ), 1, wszCount );
+ m_pFilterComboBox->AddItem( wzLocalized, pKeyValues );
+ }
+
+ // Unowned item filter
+ nCount = m_pPrimaryFilter->GetCountForFilterItem( FILTER_UNOWNED_ITEMS );
+ if ( nCount )
+ {
+ pKeyValues->SetInt( "filter", FILTER_UNOWNED_ITEMS );
+
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nCount );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_Items_Unowned" ), 1, wszCount );
+ m_pFilterComboBox->AddItem( wzLocalized, pKeyValues );
+ }
+
+#else
+ // All classes
+ if ( m_vecFilterCounts[FILTER_ALLCLASS_ITEMS] )
+ {
+ pKeyValues->SetInt( "filter", FILTER_ALLCLASS_ITEMS );
+
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecFilterCounts[FILTER_ALLCLASS_ITEMS] );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_ClassFilter_AllClasses" ), 1, wszCount );
+ m_pFilterComboBox->AddItem( wzLocalized, pKeyValues );
+ }
+
+ // Individual classes
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( m_vecFilterCounts[iClass] == 0 )
+ continue;
+
+ pKeyValues->SetInt( "filter", iClass );
+
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecFilterCounts[iClass] );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( g_szClassFilterStrings[iClass] ), 1, wszCount );
+ m_pFilterComboBox->AddItem( wzLocalized, pKeyValues );
+ }
+
+ // Unowned item filter
+ if ( m_vecFilterCounts[FILTER_UNOWNED_ITEMS] )
+ {
+ pKeyValues->SetInt( "filter", FILTER_UNOWNED_ITEMS );
+
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_vecFilterCounts[FILTER_UNOWNED_ITEMS] );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_Items_Unowned" ), 1, wszCount );
+ m_pFilterComboBox->AddItem( wzLocalized, pKeyValues );
+ }
+#endif
+
+ pKeyValues->deleteThis();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePageBase::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( m_flStartExplanationsAt && m_flStartExplanationsAt < engine->Time() )
+ {
+ m_flStartExplanationsAt = 0;
+
+ tf_explanations_store.SetValue( 1 );
+
+ CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ }
+
+ if ( !m_flStartExplanationsAt )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+}
+
diff --git a/game/client/tf/vgui/store/tf_store_page_base.h b/game/client/tf/vgui/store/tf_store_page_base.h
new file mode 100644
index 0000000..9a4bcbe
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store_page_base.h
@@ -0,0 +1,121 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PAGE_H
+#define TF_STORE_PAGE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/PropertyPage.h"
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ComboBox.h>
+#include <vgui_controls/ImagePanel.h>
+#include "econ_controls.h"
+#include "econ_ui.h"
+#include "econ_store.h"
+#include "item_model_panel.h"
+#include "store/store_page.h"
+#include "tf_shareddefs.h"
+
+class CItemModelPanel;
+class CItemModelPanelToolTip;
+class CTFPlayerModelPanel;
+class CStorePreviewItemPanel;
+class CStoreItemControlsPanel;
+
+extern const char *g_pszTipsClassImages[];
+
+#define FILTER_ALLCLASS_ITEMS TF_LAST_NORMAL_CLASS
+#define FILTER_UNOWNED_ITEMS (TF_LAST_NORMAL_CLASS + 1)
+
+//-----------------------------------------------------------------------------
+// Purpose: A player class preview icon in the store's item preview panel
+//-----------------------------------------------------------------------------
+class CStorePreviewClassIcon : public CBaseStorePreviewIcon
+{
+ DECLARE_CLASS_SIMPLE( CStorePreviewClassIcon, CBaseStorePreviewIcon );
+public:
+ CStorePreviewClassIcon( vgui::Panel *parent, const char *name ) : CBaseStorePreviewIcon(parent,name)
+ {
+ m_pImagePanel = new vgui::ImagePanel( this, "classimage" );
+ m_pImagePanel->SetShouldScaleImage( true );
+ m_pImagePanel->SetMouseInputEnabled( false );
+ m_pImagePanel->SetKeyBoardInputEnabled( false );
+ m_iClass = 0;
+ }
+
+ virtual void OnCursorEntered()
+ {
+ BaseClass::OnCursorEntered();
+ PostActionSignal(new KeyValues("ShowClassIconMouseover", "class", m_iClass));
+ }
+ virtual void OnCursorExited()
+ {
+ BaseClass::OnCursorExited();
+ PostActionSignal(new KeyValues("HideClassIconMouseover"));
+ }
+ virtual void OnMouseReleased(vgui::MouseCode code)
+ {
+ BaseClass::OnMouseReleased(code);
+ PostActionSignal(new KeyValues("ClassIconSelected", "class", m_iClass));
+ }
+
+ virtual void SetInternalImageBounds( int iX, int iY, int iWide, int iTall )
+ {
+ m_pImagePanel->SetBounds( iX, iY, iWide, iTall );
+ }
+
+ void SetClass( int iClass )
+ {
+ if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS )
+ {
+ m_pImagePanel->SetImage( g_pszTipsClassImages[iClass] );
+ }
+ else
+ {
+ m_pImagePanel->SetImage( "class_portraits/all_class" );
+ }
+ m_iClass = iClass;
+ }
+
+ int GetClass( void ) { return m_iClass; }
+
+private:
+ vgui::ImagePanel *m_pImagePanel;
+ int m_iClass;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePageBase : public CStorePage
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePageBase, CStorePage );
+protected:
+ // CTFStorePageBase should not be instantiated directly
+ CTFStorePageBase( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL );
+
+public:
+
+ virtual void OnCommand( const char *command );
+ virtual void ShowPreview( int iClass, const econ_store_entry_t* pEntry );
+
+ MESSAGE_FUNC( OnPageShow, "PageShow" );
+ MESSAGE_FUNC_PTR( OnItemDetails, "ItemDetails", panel );
+
+ virtual void UpdateFilterComboBox( void );
+ virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters );
+ virtual void OnTick( void );
+ virtual int GetNumPrimaryFilters( void ) { return FILTER_UNOWNED_ITEMS+1; }
+
+protected:
+ float m_flStartExplanationsAt;
+};
+
+#endif // TF_STORE_PAGE_H
diff --git a/game/client/tf/vgui/store/tf_store_panel_base.cpp b/game/client/tf/vgui/store/tf_store_panel_base.cpp
new file mode 100644
index 0000000..5a80fbb
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store_panel_base.cpp
@@ -0,0 +1,133 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/tf_store_panel_base.h"
+#include "vgui/IInput.h"
+#include "iclientmode.h"
+#include "econ_item_system.h"
+#include "econ_notifications.h"
+#include "c_tf_freeaccount.h"
+#include <vgui_controls/AnimationController.h>
+#include "charinfo_armory_subpanel.h"
+#include "backpack_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+class CServerNotConnectedToSteamDialog;
+CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFBaseStorePanel::CTFBaseStorePanel( Panel *parent ) : CStorePanel(parent)
+{
+ m_pArmoryPanel = new CArmoryPanel( this, "armory_panel" );
+ m_pNotificationsPresentPanel = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFBaseStorePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pNotificationsPresentPanel = FindChildByName( "NotificationsPresentPanel" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFBaseStorePanel::OnArmoryOpened( KeyValues *data )
+{
+ int iItemDef = data->GetInt( "itemdef", 0 );
+
+ // If it's a bundle, open the armory to a custom page showing all the items in the bundle
+ CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( iItemDef );
+ if ( pDef )
+ {
+ const bundleinfo_t *pBundleInfo = pDef->GetBundleInfo();
+ if ( pBundleInfo )
+ {
+ CUtlVector<item_definition_index_t> vecItems;
+ FOR_EACH_VEC( pBundleInfo->vecItemDefs, j )
+ {
+ if ( pBundleInfo->vecItemDefs[j] )
+ {
+ vecItems.AddToTail( pBundleInfo->vecItemDefs[j]->GetDefinitionIndex() );
+ }
+ }
+
+ m_pArmoryPanel->ShowPanel( pDef->GetItemBaseName(), &vecItems );
+ m_pArmoryPanel->MoveToFront();
+ return;
+ }
+ }
+
+ m_pArmoryPanel->ShowPanel( iItemDef );
+ m_pArmoryPanel->MoveToFront();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFBaseStorePanel::OnArmoryClosed( void )
+{
+ PostMessage( m_pArmoryPanel, new KeyValues("Closing") );
+ m_pArmoryPanel->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFBaseStorePanel::OnThink()
+{
+ bool bShouldBeVisible = NotificationQueue_GetNumNotifications() != 0;
+ if ( m_pNotificationsPresentPanel != NULL && m_pNotificationsPresentPanel->IsVisible() != bShouldBeVisible )
+ {
+ m_pNotificationsPresentPanel->SetVisible( bShouldBeVisible );
+ if ( bShouldBeVisible )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlink" );
+ }
+ else
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "NotificationsPresentBlinkStop" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFBaseStorePanel::PostTransactionCompleted( void )
+{
+ // pop this dialog up
+ if ( NeedsToChooseMostHelpfulFriend() )
+ {
+ // update main menu
+ IGameEvent *event = gameeventmanager->CreateEvent( "store_pricesheet_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+
+ EconUI()->GetBackpackPanel()->CheckForQuickOpenKey();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFBaseStorePanel::SetTransactionID( uint64 inID )
+{
+ BaseClass::SetTransactionID( inID );
+
+ EconUI()->GetBackpackPanel()->SetCurrentTransactionID( inID );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/store/tf_store_panel_base.h b/game/client/tf/vgui/store/tf_store_panel_base.h
new file mode 100644
index 0000000..33a7082
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store_panel_base.h
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PANEL_BASE_H
+#define TF_STORE_PANEL_BASE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/store_panel.h"
+
+class CArmoryPanel;
+class CStorePage;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFBaseStorePanel : public CStorePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFBaseStorePanel, CStorePanel );
+protected:
+ // CTFBaseStorePanel should not be instantiated directly
+ CTFBaseStorePanel( Panel *parent );
+
+public:
+ // UI Layout
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnThink();
+
+ // GC Management
+ virtual void PostTransactionCompleted( void );
+
+ // Cart Management
+ CStoreCart *GetCart( void ) { return &m_Cart; }
+ void ShowStorePanel( void );
+ void InitiateCheckout( void );
+ void CheckoutCancel( void );
+
+ virtual void SetTransactionID( uint64 inID ) OVERRIDE;
+
+ // Armory management
+ MESSAGE_FUNC_PARAMS( OnArmoryOpened, "ArmoryOpened", data );
+ MESSAGE_FUNC( OnArmoryClosed, "ArmoryClosed" );
+
+private:
+ CArmoryPanel *m_pArmoryPanel;
+ vgui::Panel *m_pNotificationsPresentPanel;
+};
+
+#endif // TF_STORE_PANEL_BASE_H
diff --git a/game/client/tf/vgui/store/tf_store_preview_item_base.cpp b/game/client/tf/vgui/store/tf_store_preview_item_base.cpp
new file mode 100644
index 0000000..9f6e2b3
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store_preview_item_base.cpp
@@ -0,0 +1,1183 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/tf_store_preview_item_base.h"
+#include "store/store_page.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "gamestringpool.h"
+#include "tf_item_inventory.h"
+#include "tf_playermodelpanel.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "c_tf_gamestats.h"
+#include "econ_ui.h"
+#include "econ_item_tools.h"
+#include "vgui_controls/MenuItem.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePreviewItemPanelBase::CTFStorePreviewItemPanelBase( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner )
+: CStorePreviewItemPanel( pParent, pResFile, "storepreviewitem", pOwner )
+{
+ ResetHandles();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::ResetHandles( void )
+{
+ m_pPlayerModelPanel = NULL;
+ m_iCurrentClass = TF_CLASS_SCOUT;
+ m_iCurrentHeldItem = 0;
+ m_pClassIconMouseoverLabel = NULL;
+ m_pRotRightButton = NULL;
+ m_pRotLeftButton = NULL;
+ m_pNextWeaponButton = NULL;
+ m_pZoomButton = NULL;
+ m_pOptionsButton = NULL;
+ m_pTeamButton = NULL;
+ m_pDataTextRichText = NULL;
+ m_iCurrentIconPosition = 0;
+ m_iState = PS_ITEM;
+ m_unPaintDef = 0;
+ m_unPaintRGB0 = 0;
+ m_unPaintRGB1 = 0;
+ m_pPaintNameLabel = NULL;
+ m_pStyleNameLabel = NULL;
+ m_pCustomizeMenu = NULL;
+ m_pClassIcons.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText )
+{
+ if ( pTargetLabel )
+ {
+ pTargetLabel->SetText( pCycleText );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ ResetHandles();
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("classmodelpanel") );
+ m_pClassIconMouseoverLabel = dynamic_cast<vgui::Label*>( FindChildByName("ClassUsageMouseoverLabel") );
+ m_pRotRightButton = dynamic_cast<CExButton*>( FindChildByName("RotRightButton") );
+ m_pRotLeftButton = dynamic_cast<CExButton*>( FindChildByName("RotLeftButton") );
+ m_pNextWeaponButton = dynamic_cast<CExButton*>( FindChildByName("NextWeaponButton") );
+ m_pZoomButton = dynamic_cast<CExButton*>( FindChildByName("ZoomButton") );
+ m_pOptionsButton = dynamic_cast<CExButton*>( FindChildByName("OptionsButton") );
+ m_pTeamButton = dynamic_cast<CExButton*>( FindChildByName("TeamButton") );
+ m_pPaintNameLabel = dynamic_cast<vgui::Label*>( FindChildByName("PaintNameLabel") );
+ m_pStyleNameLabel = dynamic_cast<vgui::Label*>( FindChildByName("StyleNameLabel") );
+
+ if ( m_pClassIconMouseoverLabel )
+ {
+ m_pClassIconMouseoverLabel->SetVisible( false );
+ }
+
+ // Find all the class images
+ CStorePreviewClassIcon *pClassImage = NULL;
+ int iIcon = 1;
+ do
+ {
+ pClassImage = dynamic_cast<CStorePreviewClassIcon*>( FindChildByName( VarArgs("ClassUsageImage%d",iIcon)) );
+ if ( pClassImage )
+ {
+ m_pClassIcons.AddToTail( pClassImage );
+ }
+ iIcon++;
+ } while ( pClassImage );
+
+ // Update our class icons. Hide them all first. The code below will unhide ones used.
+ for ( int i = 0; i < m_pClassIcons.Count(); i++ )
+ {
+ m_pClassIcons[i]->SetVisible( false );
+ }
+
+ SetState( PS_ITEM );
+
+ m_vecPaintCans.Purge();
+ const CEconItemSchema::ToolsItemDefinitionMap_t &toolDefs = GetItemSchema()->GetToolsItemDefinitionMap();
+
+ // Store all of the active paint can item defs
+ FOR_EACH_MAP_FAST( toolDefs, i )
+ {
+ const CEconItemDefinition *pItemDef = toolDefs[i];
+
+ // ignore everything that is not a paint can tool
+ const IEconTool *pEconTool = pItemDef->GetEconTool();
+ if ( pEconTool && !V_strcmp( pEconTool->GetTypeName(), "paint_can" ) )
+ {
+ m_vecPaintCans.AddToTail( pItemDef->GetDefinitionIndex() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // center the icons (we need to redo some of the work of CStorePreviewItemPanel, because we
+ // center the base item icons along with our TF specific class ones)
+ int iNumItemIcons = 0;
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ if ( m_pItemIcons[i]->IsVisible() )
+ {
+ ++iNumItemIcons;
+ }
+ }
+ int iNumClassIcons = 0;
+ FOR_EACH_VEC( m_pClassIcons, i )
+ {
+ if ( m_pClassIcons[i]->IsVisible() )
+ {
+ ++iNumClassIcons;
+ }
+ }
+ if ( iNumItemIcons || iNumClassIcons )
+ {
+ int iCenterX = GetWide() / 2;
+ int interval = XRES(2);
+ int totalWidth = (iNumItemIcons * m_pItemIcons[0]->GetWide()) + (iNumClassIcons * m_pClassIcons[0]->GetWide()) + (interval * (iNumItemIcons + iNumClassIcons - 1));
+ int iX = iCenterX - ( totalWidth / 2 );
+
+ int posX, posY;
+ m_pItemIcons[0]->GetPos( posX, posY );
+
+ int iButton = 0;
+ for ( int i = 0; i < m_pItemIcons.Count(); i++ )
+ {
+ if ( m_pItemIcons[i]->IsVisible() )
+ {
+ m_pItemIcons[i]->SetPos( iX, posY );
+ iX += m_pItemIcons[i]->GetWide() + interval;
+
+ iButton++;
+ }
+ }
+
+ for ( int i = 0; i < m_pClassIcons.Count(); i++ )
+ {
+ if ( m_pClassIcons[i]->IsVisible() )
+ {
+ m_pClassIcons[i]->SetPos( iX, posY );
+ iX += m_pClassIcons[i]->GetWide() + interval;
+
+ iButton++;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static bool IsAnythingPaintable( const CUtlVector<CEconItemView*>& vecItems )
+{
+ FOR_EACH_VEC( vecItems, i )
+ {
+ if ( vecItems[i]->GetStaticData()->GetCapabilities() & ITEM_CAP_PAINTABLE )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void UpdatePaintColorsForTeam( CTFPlayerModelPanel *pPlayerModelPanel, uint32 unRGB0, uint32 unRGB1 )
+{
+ Assert( pPlayerModelPanel );
+
+ static CSchemaAttributeDefHandle pAttrDef_ItemTintRGB( "set item tint RGB" );
+
+ if ( !pAttrDef_ItemTintRGB )
+ return;
+
+ const CUtlVector<CEconItemView*> &items = pPlayerModelPanel->GetCarriedItems();
+ if ( !IsAnythingPaintable( items ) )
+ return;
+
+ for ( int i=0; i<items.Count(); ++i )
+ {
+ if ( items[i]->GetStaticData()->GetCapabilities() & ITEM_CAP_PAINTABLE )
+ {
+ items[i]->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_ItemTintRGB, pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? unRGB0 : unRGB1 );
+ items[i]->InvalidateColor();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "team_toggle", 11 ) )
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->SetTeam( m_pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED );
+ }
+
+ // Also toggle team paint color if necessary.
+ if ( m_unPaintRGB0 != m_unPaintRGB1 )
+ {
+ UpdatePaintColorsForTeam( m_pPlayerModelPanel, m_unPaintRGB0, m_unPaintRGB1 );
+ }
+ }
+ else if ( !Q_strnicmp( command, "zoom_toggle", 11 ) )
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->ToggleZoom();
+ }
+ }
+ else if ( !Q_strnicmp( command, "paint_toggle", 12 ) )
+ {
+ CyclePaint();
+ }
+ else if ( !Q_strnicmp( command, "set_red", 7 ) )
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->SetSkin( 0 );
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "set_blu", 7 ) )
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->SetSkin( 1 );
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "next_weapon", 11 ) )
+ {
+ if ( m_pPlayerModelPanel )
+ {
+ const CUtlVector<CEconItemView*> &items = m_pPlayerModelPanel->GetCarriedItems();
+ int iLastItem = m_iCurrentHeldItem;
+ do
+ {
+ m_iCurrentHeldItem = ( m_iCurrentHeldItem + 1 ) % items.Count();
+ } while ( m_iCurrentHeldItem != iLastItem && m_pPlayerModelPanel->HoldItem( m_iCurrentHeldItem ) == false );
+ }
+ m_pPlayerModelPanel->SetTeam( m_pPlayerModelPanel->GetTeam() );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "next_style", 10 ) )
+ {
+ CycleStyle();
+ return;
+ }
+ else if ( V_strncasecmp( command, "SetPaint", V_strlen( "SetPaint" ) ) == 0 )
+ {
+ item_definition_index_t iItemDef = V_atoi( &command[ V_strlen( "SetPaint" ) ] );
+ SetPaint( iItemDef );
+ }
+ else if ( V_strncasecmp( command, "SetStyle", V_strlen( "SetStyle" ) ) == 0 )
+ {
+ style_index_t unStyle = V_atoi( &command[ V_strlen( "SetStyle" ) ] );
+ SetStyle( unStyle );
+ }
+ else if ( V_strncasecmp( command, "SetUnusual", V_strlen( "SetUnusual" ) ) == 0 )
+ {
+ int iUnusual = V_atoi( &command[ V_strlen( "SetUnusual" ) ] );
+ SetUnusual( iUnusual );
+ }
+ else if ( FStrEq( command, "options" ) )
+ {
+ UpdateCustomizeMenu();
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::OnClassIconSelected( KeyValues *data )
+{
+ int iClass = data->GetInt( "class", 0 );
+ if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS )
+ {
+ iClass = TF_CLASS_SCOUT;
+ }
+ m_iCurrentClass = iClass;
+ UpdateModelPanel();
+
+ SetState( PS_PLAYER );
+
+// C_CTF_GameStats.Event_Store( IE_STORE_ITEM_PREVIEWED, NULL, NULL, m_iCurrentClass );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::SetPlayerModelVisible( bool bVisible )
+{
+ if( m_pPlayerModelPanel )
+ {
+ m_pPlayerModelPanel->SetVisible( bVisible );
+ if ( m_pRotRightButton )
+ {
+ m_pRotRightButton->SetVisible( bVisible );
+ }
+ if ( m_pRotLeftButton )
+ {
+ m_pRotLeftButton->SetVisible( bVisible );
+ }
+ UpdatePlayerModelButtons();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry )
+{
+ m_iCurrentClass = 0;
+ BaseClass::PreviewItem( iClass, pItem, pEntry );
+
+ UpdateModelPanel();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::SetState( preview_state_t iState )
+{
+ BaseClass::SetState( iState );
+ SetPlayerModelVisible( m_iState == PS_PLAYER );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFStorePreviewItemPanelBase::GetPreviewTeam() const
+{
+ return m_pPlayerModelPanel
+ ? m_pPlayerModelPanel->GetTeam()
+ : TF_TEAM_RED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdateIcons( void )
+{
+ // Don't bother calling back to the base class UpdateIcons, because
+ // we'd need to redo it all with the class icons factored in.
+
+ bool bAdditionalIcons = false;
+
+ // Do the item icons first
+ if ( m_iState == PS_DETAILS )
+ {
+ // Show as many of the items in the bundle as possible
+ const CEconItemDefinition *pItemData = m_item.GetItemDefinition();
+ if ( pItemData )
+ {
+ const bundleinfo_t *pBundleInfo = pItemData->GetBundleInfo();
+ if ( pBundleInfo )
+ {
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ // If we haven't scrolled, the first item is the bundle itself
+ if ( m_iCurrentIconPosition == 0 && i == 0 )
+ {
+ m_pItemIcons[0]->SetItem( 0, &m_item );
+ continue;
+ }
+
+ int iItemPos = (i - 1 + m_iCurrentIconPosition);
+ if ( pBundleInfo->vecItemDefs.Count() > iItemPos && pBundleInfo->vecItemDefs[iItemPos] )
+ {
+ m_pItemIcons[i]->SetItem( i, pBundleInfo->vecItemDefs[iItemPos]->GetDefinitionIndex() );
+ m_pItemIcons[i]->SetVisible( true );
+ }
+ else
+ {
+ m_pItemIcons[i]->SetVisible( false );
+ }
+ }
+
+ bAdditionalIcons = (m_iCurrentIconPosition + m_pItemIcons.Count()) <= pBundleInfo->vecItemDefs.Count();
+ }
+ else if ( m_pItemIcons.Count() )
+ {
+ m_pItemIcons[0]->SetVisible( true );
+ m_pItemIcons[0]->SetItem( 0, &m_item );
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ if ( i != 0 )
+ {
+ m_pItemIcons[i]->SetVisible( false );
+ }
+ }
+ }
+ }
+
+ // Hide all the class icons
+ for ( int i = 0; i < m_pClassIcons.Count(); i++ )
+ {
+ m_pClassIcons[i]->SetVisible( false );
+ }
+ }
+ else
+ {
+ // Hide all item icons first (but not the first if we haven't scrolled)
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ m_pItemIcons[i]->SetVisible( m_iCurrentIconPosition == 0 && i == 0 );
+ }
+
+ // First icon is always the store entry (item/bundle), if we haven't scrolled right
+ if ( m_iCurrentIconPosition == 0 && m_pItemIcons.Count() )
+ {
+ m_pItemIcons[0]->SetItem( 0, &m_item );
+ }
+
+ // Then do the class icons
+ const CTFItemDefinition *pItemData = m_item.GetItemDefinition();
+ if ( pItemData )
+ {
+ int iButton = 0;
+ int iMaxButtons = m_pClassIcons.Count();
+ int iNumClasses = (TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS);
+ // we show one less class icon when the item is visible
+ if ( iMaxButtons < iNumClasses && m_iCurrentIconPosition == 0 )
+ {
+ iMaxButtons -= 1;
+ }
+ for ( int iClass = TF_FIRST_NORMAL_CLASS + m_iCurrentIconPosition; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( !pItemData->CanBeUsedByClass(iClass) )
+ continue;
+
+ // Run out of buttons?
+ if ( iButton >= iMaxButtons )
+ {
+ bAdditionalIcons = true;
+ break;
+ }
+
+ m_pClassIcons[iButton]->SetVisible( true );
+ m_pClassIcons[iButton]->SetClass(iClass);
+ iButton++;
+
+ if ( !m_iCurrentClass )
+ {
+ m_iCurrentClass = iClass;
+ }
+ }
+ for ( ; iButton < m_pClassIcons.Count(); ++iButton )
+ {
+ m_pClassIcons[iButton]->SetVisible( false );
+ }
+ }
+ }
+
+ if( m_pIconsMoveLeftButton )
+ m_pIconsMoveLeftButton->SetVisible( (m_iCurrentIconPosition > 0) );
+ if( m_pIconsMoveRightButton )
+ m_pIconsMoveRightButton->SetVisible( bAdditionalIcons );
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( !IsVisible() )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ return;
+ }
+
+ if ( m_iCurrentRotation )
+ {
+ m_pPlayerModelPanel->RotateYaw( m_iCurrentRotation );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdateModelPanel()
+{
+ if ( m_pPlayerModelPanel )
+ {
+ m_iCurrentHeldItem = 0;
+ m_pPlayerModelPanel->SetToPlayerClass( m_iCurrentClass, false );
+ m_pPlayerModelPanel->ClearCarriedItems();
+
+
+ if ( m_item.IsValid() )
+ {
+ CTFItemDefinition *pItemDef = m_item.GetStaticData();
+ if ( pItemDef->GetBundleInfo() != NULL )
+ {
+ const bundleinfo_t *pBundleInfo = pItemDef->GetBundleInfo();
+ FOR_EACH_VEC( pBundleInfo->vecItemDefs, i )
+ {
+ CTFItemDefinition *pBundledItem = dynamic_cast<CTFItemDefinition *>( pBundleInfo->vecItemDefs[i] );
+ if ( pBundledItem && pBundledItem->CanBeUsedByClass( m_iCurrentClass ) )
+ {
+ CEconItemView bundleItemData;
+ bundleItemData.Init( pBundledItem->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ bundleItemData.SetClientItemFlags( kEconItemFlagClient_Preview );
+ int iItemIdx = m_pPlayerModelPanel->AddCarriedItem( &bundleItemData );
+ // try to hold it
+ if ( m_pPlayerModelPanel->HoldItem( iItemIdx ) )
+ {
+ m_iCurrentHeldItem = iItemIdx;
+ }
+ }
+ }
+ }
+ else
+ {
+ m_pPlayerModelPanel->AddCarriedItem( &m_item );
+
+ // Now make sure we're holding it if it's a non-wearable
+ int iLoadoutSlot = m_item.GetStaticData()->GetLoadoutSlot( m_iCurrentClass );
+ m_pPlayerModelPanel->HoldItemInSlot( iLoadoutSlot );
+ }
+ }
+
+ UpdatePlayerModelButtons();
+
+ // Fix a problem where changing the class would change the preview mesh but wouldn't
+ // update the paint. This is a hack and won't work if we have multi-class styles or
+ // just about anything else.
+ CyclePaint( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdatePlayerModelButtons()
+{
+ UpdateOptionsButton();
+ UpdateNextWeaponButton();
+ UpdateZoomButton();
+ UpdateTeamButton();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdateCustomizeMenu( void )
+{
+ if ( m_pCustomizeMenu )
+ {
+ delete m_pCustomizeMenu;
+ m_pCustomizeMenu = NULL;
+ }
+
+ if ( !m_pPlayerModelPanel->IsVisible() )
+ return;
+
+ if ( !m_pOptionsButton || !m_pOptionsButton->IsVisible() )
+ return;
+
+ if ( !m_item.IsValid() )
+ return;
+
+ m_pCustomizeMenu = new Menu( this, "CustomizeMenu" );
+ MenuBuilder contextMenuBuilder( m_pCustomizeMenu, this );
+ const char *pszContextMenuBorder = "NotificationDefault";
+ const char *pszContextMenuFont = "HudFontMediumSecondary";
+ m_pCustomizeMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ m_pCustomizeMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+ // Add paint options sub menu
+ {
+ Menu *pPaintSubMenu = NULL;
+ FOR_EACH_VEC( m_vecPaintCans, i )
+ {
+ item_definition_index_t paintItemDefIndex = m_vecPaintCans[ i ];
+ GameItemDefinition_t * pPaintCanDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinition( paintItemDefIndex ) );
+ if ( !CEconSharedToolSupport::ToolCanApplyToDefinition( dynamic_cast<const GameItemDefinition_t *>( pPaintCanDef ), m_item.GetStaticData() ) )
+ continue;
+
+ if ( pPaintSubMenu == NULL )
+ {
+ pPaintSubMenu = new Menu( this, "PaintSubMenu" );
+ pPaintSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pPaintSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ contextMenuBuilder.AddCascadingMenuItem( "#Context_Paint", pPaintSubMenu, "customization" );
+ }
+
+ wchar_t wBuff[256] = { 0 };
+ V_swprintf_safe( wBuff, L" %ls", g_pVGuiLocalize->Find( pPaintCanDef->GetItemBaseName() ) );
+ int nIndex = pPaintSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "SetPaint%d", paintItemDefIndex ) ), this );
+ vgui::MenuItem *pMenuItem = pPaintSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( wBuff );
+ pMenuItem->InvalidateLayout( true, false );
+
+ uint32 unPaintRGB0 = 0;
+ uint32 unPaintRGB1 = 0;
+
+ static CSchemaAttributeDefHandle pAttrDef_PaintRGB( "set item tint RGB" );
+ static CSchemaAttributeDefHandle pAttrDef_PaintRGB2( "set item tint RGB 2" );
+
+ float fRGB = 0.0f;
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pPaintCanDef, pAttrDef_PaintRGB, &fRGB ) && fRGB != 0.0f )
+ {
+ unPaintRGB0 = fRGB;
+
+ // We may or may not have a secondary paint color as well. If we don't, we just use the primary
+ // paint color to fill both slots.
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pPaintCanDef, pAttrDef_PaintRGB2, &fRGB ) )
+ {
+ unPaintRGB1 = fRGB;
+ }
+ else
+ {
+ unPaintRGB1 = unPaintRGB0;
+ }
+ }
+
+ CItemMaterialCustomizationIconPanel *pCustomPanel = new CItemMaterialCustomizationIconPanel( pMenuItem, "paint" );
+ pCustomPanel->SetZPos( -100 );
+ pCustomPanel->SetTall( 30 );
+ pCustomPanel->SetWide( 30 );
+ pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( (unPaintRGB0 & 0xFF0000) >> 16, 0, 255 ), clamp( (unPaintRGB0 & 0xFF00) >> 8, 0, 255 ), clamp( (unPaintRGB0 & 0xFF), 0, 255 ), 255 ) );
+ pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( (unPaintRGB1 & 0xFF0000) >> 16, 0, 255 ), clamp( (unPaintRGB1 & 0xFF00) >> 8, 0, 255 ), clamp( (unPaintRGB1 & 0xFF), 0, 255 ), 255 ) );
+ }
+ }
+
+ // Add style
+ {
+ Menu *pStyleSubMenu = NULL;
+ if ( m_item.GetStaticData()->GetNumSelectableStyles() > 1 )
+ {
+ for ( style_index_t unStyle=0; unStyle<m_item.GetStaticData()->GetNumStyles(); ++unStyle )
+ {
+ const CEconStyleInfo *pStyle = m_item.GetStaticData()->GetStyleInfo( unStyle );
+ if ( !pStyle )
+ continue;
+
+ if ( !pStyle->IsSelectable() )
+ continue;
+
+ if ( pStyleSubMenu == NULL )
+ {
+ pStyleSubMenu = new Menu( this, "StyleSubMenu" );
+ pStyleSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pStyleSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ contextMenuBuilder.AddCascadingMenuItem( "#Context_Style", pStyleSubMenu, "customization" );
+ }
+
+ int nIndex = pStyleSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "SetStyle%d", unStyle ) ), this );
+ vgui::MenuItem *pMenuItem = pStyleSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( pStyle->GetName() );
+ pMenuItem->InvalidateLayout( true, false );
+ }
+ }
+ }
+
+ // Add unusual
+ {
+ const CUtlVector< int > *pUnusualList = GetUnusualList();
+ if ( pUnusualList )
+ {
+ Menu *pUnusualSubMenu = NULL;
+ for ( int i=0; i<pUnusualList->Count(); ++i )
+ {
+ if ( pUnusualSubMenu == NULL )
+ {
+ pUnusualSubMenu = new Menu( this, "UnusualSubMenu" );
+ pUnusualSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pUnusualSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ contextMenuBuilder.AddCascadingMenuItem( "#Context_Unusual", pUnusualSubMenu, "customization" );
+ }
+
+ int iParticleIndex = pUnusualList->Element( i );
+ int nIndex = pUnusualSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "SetUnusual%d", iParticleIndex ) ), this );
+ vgui::MenuItem *pMenuItem = pUnusualSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( GetItemSchema()->GetParticleSystemLocalizedName( iParticleIndex ) );
+ pMenuItem->InvalidateLayout( true, false );
+ }
+ }
+ }
+
+ int nX, nY;
+ g_pVGuiInput->GetCursorPosition( nX, nY );
+ m_pCustomizeMenu->SetPos( nX - 1, nY - 1 );
+
+ m_pCustomizeMenu->SetVisible(true);
+ m_pCustomizeMenu->AddActionSignalTarget(this);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdateOptionsButton( void )
+{
+ if ( !m_pOptionsButton )
+ return;
+
+ m_pOptionsButton->SetVisible( false );
+
+ if ( !m_pPlayerModelPanel->IsVisible() )
+ return;
+
+ if ( !m_item.IsValid() )
+ return;
+
+ const CEconItemDefinition *pItemData = m_item.GetItemDefinition();
+ if ( !pItemData )
+ return;
+
+ bool bVisible = false;
+
+ // Is the selected item paintable
+ if ( pItemData->GetCapabilities() & ITEM_CAP_PAINTABLE )
+ {
+ bVisible = true;
+ }
+ // has multiple styles?
+ else if ( pItemData->GetNumSelectableStyles() > 1 )
+ {
+ bVisible = true;
+ }
+ // can have unusual?
+ else if ( GetUnusualList() != NULL )
+ {
+ bVisible = true;
+ }
+
+ m_pOptionsButton->SetVisible( bVisible );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdateZoomButton( void )
+{
+ if ( !m_pZoomButton )
+ return;
+
+ m_pZoomButton->SetVisible( m_pPlayerModelPanel->IsVisible() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdateTeamButton( void )
+{
+ if ( !m_pTeamButton )
+ return;
+
+ m_pTeamButton->SetVisible( m_pPlayerModelPanel->IsVisible() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::UpdateNextWeaponButton( void )
+{
+ if ( !m_pNextWeaponButton )
+ return;
+
+ if ( !m_pPlayerModelPanel->IsVisible() )
+ {
+ m_pNextWeaponButton->SetVisible( false );
+ return;
+ }
+
+ bool bShowNextWeaponsButton = false;
+ const CUtlVector<CEconItemView*> &items = m_pPlayerModelPanel->GetCarriedItems();
+ int iNumItemsArray[CLASS_LOADOUT_POSITION_COUNT];
+ memset( iNumItemsArray, 0, sizeof( iNumItemsArray ) );
+ FOR_EACH_VEC( items, i )
+ {
+ CEconItemView *pItem = items[i];
+ int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClass );
+ if ( iLoadoutSlot >= 0 && iLoadoutSlot < CLASS_LOADOUT_POSITION_COUNT )
+ {
+ ++iNumItemsArray[iLoadoutSlot];
+ }
+ }
+ bShowNextWeaponsButton |= iNumItemsArray[LOADOUT_POSITION_PRIMARY] + iNumItemsArray[LOADOUT_POSITION_SECONDARY] + iNumItemsArray[LOADOUT_POSITION_MELEE] > 1;
+
+ m_pNextWeaponButton->SetVisible( bShowNextWeaponsButton );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::OnHideClassIconMouseover( void )
+{
+ if ( m_pClassIconMouseoverLabel )
+ {
+ m_pClassIconMouseoverLabel->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::OnShowClassIconMouseover( KeyValues *data )
+{
+ if ( m_pClassIconMouseoverLabel )
+ {
+ const CEconItemDefinition *pItemData = m_item.GetItemDefinition();
+ bool bIsABundle = pItemData ? (pItemData->GetBundleInfo() != NULL) : false;
+
+ // 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 = bIsABundle ? "#Store_ClassImageMouseoverBundle" : "#Store_ClassImageMouseover";
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( pszLocString ), 1, g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) );
+ m_pClassIconMouseoverLabel->SetText( wzLocalized );
+ }
+ else
+ {
+ const char *pszLocString = bIsABundle ? "#Store_ClassImageMouseoverAllBundle" : "#Store_ClassImageMouseoverAll";
+ m_pClassIconMouseoverLabel->SetText( pszLocString );
+ }
+
+ m_pClassIconMouseoverLabel->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::CycleStyle( void )
+{
+ if ( !m_pPlayerModelPanel )
+ return;
+
+ // Find and cycle the style based on the first item we're previewing that has
+ // styles. If we are previewing multiple items at the same time where more than
+ // one of them has styles, we'll only cycle through the names/options of the
+ // first one in the list.
+ CEconItemView *pPreviewItemView = NULL;
+
+ const CUtlVector<CEconItemView*> &vecItems = m_pPlayerModelPanel->GetCarriedItems();
+ FOR_EACH_VEC( vecItems, i )
+ {
+ CEconItemView *pItem = vecItems[i];
+ if ( pItem->GetStaticData()->GetNumStyles() && ( pItem->GetFlags() & kEconItemFlagClient_Preview ) )
+ {
+ pPreviewItemView = pItem;
+ break;
+ }
+ }
+
+ if ( !pPreviewItemView )
+ return;
+
+ // Cycle.
+ style_index_t unStyleCount = pPreviewItemView->GetStaticData()->GetNumStyles();
+ Assert( unStyleCount >= 1 );
+
+ style_index_t unStyle = pPreviewItemView->GetItemStyle();
+ // Default to style 0 if we're getting an invalid index
+ unStyle = unStyle == INVALID_STYLE_INDEX ? 0 : unStyle;
+
+ // Try to find the next selectable style
+ const CEconStyleInfo *pStyle = NULL;
+ style_index_t unStartingStyle = unStyle;
+ do
+ {
+ unStyle = (unStyle + 1) % unStyleCount;
+ pStyle = pPreviewItemView->GetStaticData()->GetStyleInfo( unStyle );
+ Assert( pStyle );
+ }
+ while ( unStyle != unStartingStyle && ( !pStyle || !pStyle->IsSelectable() ) );
+
+ pPreviewItemView->SetItemStyleOverride( unStyle );
+ SetCycleLabelText( m_pStyleNameLabel, pStyle->GetName() );
+
+ // Re-equip our held item. This causes all of our equipped items to get reloaded, and thus
+ // their styles updated
+ m_pPlayerModelPanel->SwitchHeldItemTo( m_pPlayerModelPanel->GetHeldItem() );
+ m_pPlayerModelPanel->UpdatePreviewVisuals();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::SetPaint( item_definition_index_t iItemDef )
+{
+ if ( !m_pPlayerModelPanel )
+ return;
+
+ if ( !IsAnythingPaintable( m_pPlayerModelPanel->GetCarriedItems() ) )
+ return;
+
+ static CSchemaAttributeDefHandle pAttribDef_Paint( "set item tint RGB" );
+ static CSchemaAttributeDefHandle pAttribDef_Paint2( "set item tint RGB 2" );
+
+ // Find the next paint color.
+ const CEconItemSchema::SortedItemDefinitionMap_t &mapDefs = GetItemSchema()->GetSortedItemDefinitionMap();
+
+ m_unPaintRGB0 = 0;
+ m_unPaintRGB1 = 0;
+
+ if ( iItemDef != INVALID_ITEM_DEF_INDEX )
+ {
+ int iteratorName = mapDefs.FirstInorder();
+ while ( iteratorName != mapDefs.InvalidIndex() )
+ {
+ // Find the next sub
+ int iIndex = mapDefs[iteratorName]->GetDefinitionIndex();
+ if ( iIndex == iItemDef )
+ {
+ // Is this definition something that has paint attributes on it?
+ const CEconItemDefinition *pData = mapDefs[iteratorName];
+
+ float fRGB = 0.0f;
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint, &fRGB ) && fRGB != 0.0f )
+ {
+ m_unPaintRGB0 = fRGB;
+
+ // We may or may not have a secondary paint color as well. If we don't, we just use the primary
+ // paint color to fill both slots.
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint2, &fRGB ) )
+ {
+ m_unPaintRGB1 = fRGB;
+ }
+ else
+ {
+ m_unPaintRGB1 = m_unPaintRGB0;
+ }
+
+ m_unPaintDef = pData->GetDefinitionIndex();
+ SetCycleLabelText( m_pPaintNameLabel, pData->GetItemBaseName() );
+ }
+ else
+ {
+ Warning( "CTFStorePreviewItemPanelBase::SetPaint iItemDef[%d] is not paint item", iItemDef );
+ }
+
+ break;
+ }
+
+ iteratorName = mapDefs.NextInorder( iteratorName );
+ }
+ }
+
+ if ( m_unPaintRGB0 == 0 )
+ {
+ m_unPaintDef = 0;
+ SetCycleLabelText( m_pPaintNameLabel, "#Store_NoPaint" );
+ }
+
+ UpdatePaintColorsForTeam( m_pPlayerModelPanel, m_unPaintRGB0, m_unPaintRGB1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::SetStyle( style_index_t unStyle )
+{
+ if ( !m_pPlayerModelPanel )
+ return;
+
+ // Find and cycle the style based on the first item we're previewing that has
+ // styles. If we are previewing multiple items at the same time where more than
+ // one of them has styles, we'll only cycle through the names/options of the
+ // first one in the list.
+ CEconItemView *pPreviewItemView = NULL;
+
+ const CUtlVector<CEconItemView*> &vecItems = m_pPlayerModelPanel->GetCarriedItems();
+ FOR_EACH_VEC( vecItems, i )
+ {
+ CEconItemView *pItem = vecItems[i];
+ if ( pItem->GetStaticData()->GetNumSelectableStyles() > 1 )
+ {
+ pPreviewItemView = pItem;
+ break;
+ }
+ }
+
+ if ( !pPreviewItemView )
+ return;
+
+ pPreviewItemView->SetItemStyleOverride( unStyle );
+ const CEconStyleInfo *pStyle = pPreviewItemView->GetStaticData()->GetStyleInfo( unStyle );
+ Assert( pStyle && pStyle->IsSelectable() );
+ if ( pStyle )
+ {
+ SetCycleLabelText( m_pStyleNameLabel, pStyle->GetName() );
+ }
+
+ // Re-equip our held item. This causes all of our equipped items to get reloaded, and thus
+ // their styles updated
+ m_pPlayerModelPanel->SwitchHeldItemTo( m_pPlayerModelPanel->GetHeldItem() );
+ m_pPlayerModelPanel->UpdatePreviewVisuals();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::SetUnusual( uint32 iUnusualIndex )
+{
+ if ( !m_pPlayerModelPanel )
+ return;
+
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" );
+ const CUtlVector<CEconItemView*> &vecItems = m_pPlayerModelPanel->GetCarriedItems();
+
+ FOR_EACH_VEC( vecItems, i )
+ {
+ CEconItemView *pItem = vecItems[i];
+ if ( pItem->GetStaticData()->GetTauntData() )
+ {
+ const float& value_as_float = (float&)iUnusualIndex;
+ pItem->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_OnTauntAttachParticleIndex, value_as_float );
+ }
+ else
+ {
+ pItem->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_AttachParticleEffect, iUnusualIndex );
+ }
+ }
+ m_pPlayerModelPanel->InvalidateParticleEffects();
+ m_pPlayerModelPanel->SwitchHeldItemTo( m_pPlayerModelPanel->GetHeldItem() );
+}
+
+
+const CUtlVector< int > *CTFStorePreviewItemPanelBase::GetUnusualList() const
+{
+ if ( !AllowUnusualPreview() )
+ return NULL;
+
+ int iLoadoutSlot = m_item.GetStaticData()->GetLoadoutSlot( m_iCurrentClass );
+ if ( IsValidPickupWeaponSlot( iLoadoutSlot ) )
+ {
+ return GetItemSchema()->GetWeaponUnusualParticleIndexes();
+ }
+ // is hat or whole head?
+ else if ( m_item.GetItemDefinition()->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "hat" ) || m_item.GetItemDefinition()->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "whole_head" ) )
+ {
+ return GetItemSchema()->GetCosmeticUnusualParticleIndexes();
+ }
+ else if ( IsTauntSlot( iLoadoutSlot ) )
+ {
+ return GetItemSchema()->GetTauntUnusualParticleIndexes();
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanelBase::CyclePaint( bool bActuallyCycle )
+{
+ if ( !m_pPlayerModelPanel )
+ return;
+
+ if ( !IsAnythingPaintable( m_pPlayerModelPanel->GetCarriedItems() ) )
+ return;
+
+ static CSchemaAttributeDefHandle pAttribDef_Paint( "set item tint RGB" );
+ static CSchemaAttributeDefHandle pAttribDef_Paint2( "set item tint RGB 2" );
+
+ // Find the next paint color.
+ const CEconItemSchema::SortedItemDefinitionMap_t &mapDefs = GetItemSchema()->GetSortedItemDefinitionMap();
+
+ m_unPaintRGB0 = 0;
+ m_unPaintRGB1 = 0;
+
+ if ( bActuallyCycle || m_unPaintDef > 0 )
+ {
+ int iteratorName = mapDefs.FirstInorder();
+ while ( iteratorName != mapDefs.InvalidIndex() )
+ {
+ // Find the next sub
+ int iIndex = mapDefs[iteratorName]->GetDefinitionIndex();
+ if ( bActuallyCycle ? iIndex > m_unPaintDef : iIndex >= m_unPaintDef )
+ {
+ // Is this definition something that has paint attributes on it?
+ const CEconItemDefinition *pData = mapDefs[iteratorName];
+
+ float fRGB = 0.0f;
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint, &fRGB ) && fRGB != 0.0f )
+ {
+ if ( V_strstr( pData->GetItemBaseName(), "Halloween" ) )
+ {
+ iteratorName = mapDefs.NextInorder( iteratorName );
+ continue;
+ }
+
+ m_unPaintRGB0 = fRGB;
+
+ // We may or may not have a secondary paint color as well. If we don't, we just use the primary
+ // paint color to fill both slots.
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pData, pAttribDef_Paint2, &fRGB ) )
+ {
+ m_unPaintRGB1 = fRGB;
+ }
+ else
+ {
+ m_unPaintRGB1 = m_unPaintRGB0;
+ }
+
+ m_unPaintDef = pData->GetDefinitionIndex();
+ SetCycleLabelText( m_pPaintNameLabel, pData->GetItemBaseName() );
+ break;
+ }
+ }
+
+ iteratorName = mapDefs.NextInorder( iteratorName );
+ }
+ }
+
+ if ( m_unPaintRGB0 == 0 )
+ {
+ m_unPaintDef = 0;
+ SetCycleLabelText( m_pPaintNameLabel, "#Store_NoPaint" );
+ }
+
+ UpdatePaintColorsForTeam( m_pPlayerModelPanel, m_unPaintRGB0, m_unPaintRGB1 );
+}
diff --git a/game/client/tf/vgui/store/tf_store_preview_item_base.h b/game/client/tf/vgui/store/tf_store_preview_item_base.h
new file mode 100644
index 0000000..cd382a0
--- /dev/null
+++ b/game/client/tf/vgui/store/tf_store_preview_item_base.h
@@ -0,0 +1,102 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PREVIEW_ITEM_BASE_H
+#define TF_STORE_PREVIEW_ITEM_BASE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include "store/store_preview_item.h"
+#include "store/v1/tf_store_page.h"
+#include "tf_shareddefs.h"
+#include "tf_hud_mainmenuoverride.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePreviewItemPanelBase : public CStorePreviewItemPanel
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePreviewItemPanelBase, CStorePreviewItemPanel );
+protected:
+ // CTFStorePreviewItemPanelBase should not be intantiated directly
+ CTFStorePreviewItemPanelBase( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner );
+
+public:
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void OnTick( void );
+
+ virtual void PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL ) OVERRIDE;
+ virtual void SetState( preview_state_t iState );
+
+ virtual int GetPreviewTeam() const;
+
+ MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data );
+ MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" );
+ MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data );
+
+protected:
+ void UpdateModelPanel();
+ virtual void SetPlayerModelVisible( bool bVisible );
+ virtual void UpdatePlayerModelButtons( void );
+ virtual void UpdateCustomizeMenu( void );
+ void UpdateOptionsButton( void );
+ void UpdateNextWeaponButton( void );
+ void UpdateZoomButton( void );
+ void UpdateTeamButton( void );
+ virtual void UpdateIcons( void );
+
+ void SetPaint( item_definition_index_t iItemDef );
+ void SetStyle( style_index_t unStyle );
+ void SetUnusual( uint32 iUnusualIndex );
+ const CUtlVector< int > *GetUnusualList() const;
+ virtual bool AllowUnusualPreview() const
+ {
+#ifdef STAGING_ONLY
+ // we want to be able to use this everywhere in staging for testing purpose
+ return true;
+#else
+ return false;
+#endif
+ }
+
+ void CyclePaint( bool bActuallyCycle = true );
+ void CycleStyle( void );
+ void ResetHandles( void );
+
+ // This can be overridden to capture *any* "cycle text" that is being set, so one generic label can be used
+ // by a derived class if needed. Base version just sets the label's text.
+ virtual void SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText );
+
+ vgui::Label *m_pClassIconMouseoverLabel;
+ CTFPlayerModelPanel *m_pPlayerModelPanel;
+ CUtlVector<CStorePreviewClassIcon*> m_pClassIcons;
+
+ int m_iCurrentClass;
+ int m_iCurrentHeldItem;
+
+ item_definition_index_t m_unPaintDef;
+ uint32 m_unPaintRGB0;
+ uint32 m_unPaintRGB1;
+
+ CExButton *m_pRotRightButton;
+ CExButton *m_pRotLeftButton;
+ CExButton *m_pNextWeaponButton;
+ CExButton *m_pZoomButton;
+ CExButton *m_pOptionsButton;
+ CExButton *m_pTeamButton;
+ vgui::Label *m_pPaintNameLabel;
+ vgui::Label *m_pStyleNameLabel;
+
+ Menu *m_pCustomizeMenu;
+ CUtlVector< item_definition_index_t > m_vecPaintCans;
+};
+
+#endif // TF_STORE_PREVIEW_ITEM_H
diff --git a/game/client/tf/vgui/store/v1/tf_store_page.cpp b/game/client/tf/vgui/store/v1/tf_store_page.cpp
new file mode 100644
index 0000000..9d55a9c
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_page.cpp
@@ -0,0 +1,96 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v1/tf_store_page.h"
+#include "store/v1/tf_store_preview_item.h"
+#include "c_tf_freeaccount.h"
+#include "store/store_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage1::CTFStorePage1(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile ) : BaseClass(parent, pPageData, pPreviewItemResFile)
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFStorePage1::GetPageResFile( void )
+{
+ Assert( !"No code should currently reference the old store!" );
+
+ return m_pPageData->m_pchPageRes;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage1::OnPageShow( void )
+{
+ BaseClass::OnPageShow();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage1::OnCommand( const char *command )
+{
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage1::OnItemDetails( vgui::Panel *panel )
+{
+ BaseClass::OnItemDetails( panel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage1::ShowPreview( int iClass, const econ_store_entry_t* pEntry )
+{
+ BaseClass::ShowPreview( iClass, pEntry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage1::UpdateFilterComboBox( void )
+{
+ BaseClass::UpdateFilterComboBox();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage1::GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters )
+{
+ return BaseClass::GetFiltersForDef( pDef, pVecFilters );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage1::OnTick( void )
+{
+ BaseClass::OnTick();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel *CTFStorePage1::CreatePreviewPanel( void )
+{
+ return new CTFStorePreviewItemPanel1( this, m_pPreviewItemResFile, "storepreviewitem", this );
+}
diff --git a/game/client/tf/vgui/store/v1/tf_store_page.h b/game/client/tf/vgui/store/v1/tf_store_page.h
new file mode 100644
index 0000000..814bc01
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_page.h
@@ -0,0 +1,39 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PAGE1_H
+#define TF_STORE_PAGE1_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/tf_store_page_base.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage1 : public CTFStorePageBase
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage1, CTFStorePageBase );
+public:
+ CTFStorePage1( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL );
+
+ virtual const char *GetPageResFile( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPreview( int iClass, const econ_store_entry_t* pEntry );
+
+ MESSAGE_FUNC( OnPageShow, "PageShow" );
+ MESSAGE_FUNC_PTR( OnItemDetails, "ItemDetails", panel );
+
+ virtual void UpdateFilterComboBox( void );
+ virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters );
+ virtual void OnTick( void );
+
+ virtual CStorePreviewItemPanel *CreatePreviewPanel( void );
+};
+
+#endif // TF_STORE_PAGE1_H
diff --git a/game/client/tf/vgui/store/v1/tf_store_page_maps.cpp b/game/client/tf/vgui/store/v1/tf_store_page_maps.cpp
new file mode 100644
index 0000000..4b8a895
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_page_maps.cpp
@@ -0,0 +1,30 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v1/tf_store_page_maps.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage_Maps::CTFStorePage_Maps( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData )
+: BaseClass( parent, pPageData, "Resource/UI/econ/store/v1/StorePreviewItemPanel_Maps.res" )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage_Maps::OnPageShow()
+{
+ BaseClass::OnPageShow();
+
+ SetDetailsVisible( false );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/store/v1/tf_store_page_maps.h b/game/client/tf/vgui/store/v1/tf_store_page_maps.h
new file mode 100644
index 0000000..30fc0dc
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_page_maps.h
@@ -0,0 +1,33 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_MAPS_H
+#define STORE_PAGE_MAPS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/v1/tf_store_page.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage_Maps : public CTFStorePage1
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage_Maps, CTFStorePage1 );
+public:
+ CTFStorePage_Maps( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+ virtual ~CTFStorePage_Maps() {}
+
+ virtual const char* GetPageResFile() { return "Resource/UI/econ/store/v1/StorePage_Maps.res"; }
+
+ virtual void OnPageShow( void );
+
+protected:
+};
+
+#endif // STORE_PAGE_MAPS_H
diff --git a/game/client/tf/vgui/store/v1/tf_store_panel.cpp b/game/client/tf/vgui/store/v1/tf_store_panel.cpp
new file mode 100644
index 0000000..4daf037
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_panel.cpp
@@ -0,0 +1,69 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v1/tf_store_page.h"
+#include "store/v1/tf_store_panel.h"
+#include "store/store_page_halloween.h"
+#include "store/store_page_new.h"
+#include "store/v1/tf_store_page_maps.h"
+#include "store/store_viewcart.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePanel1::CTFStorePanel1( vgui::Panel *parent ) : CTFBaseStorePanel(parent)
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePanel1::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePanel1::OnThink()
+{
+ BaseClass::OnThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePanel1::PostTransactionCompleted( void )
+{
+ BaseClass::PostTransactionCompleted();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static store page factory.
+//-----------------------------------------------------------------------------
+CStorePage *CTFStorePanel1::CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData )
+{
+ if ( pPageData )
+ {
+ if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_SpecialPromo" ) )
+ return new CTFStorePage_SpecialPromo( this, pPageData );
+
+ if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Maps" ) )
+ return new CTFStorePage_Maps( this, pPageData );
+
+ if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Popular" ) )
+ return new CTFStorePage_Popular( this, pPageData );
+ }
+
+ // Default, standard store page.
+ return new CTFStorePage1( this, pPageData );
+}
diff --git a/game/client/tf/vgui/store/v1/tf_store_panel.h b/game/client/tf/vgui/store/v1/tf_store_panel.h
new file mode 100644
index 0000000..cac6b00
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_panel.h
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PANEL1_H
+#define TF_STORE_PANEL1_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/tf_store_panel_base.h"
+
+class CStorePage;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePanel1 : public CTFBaseStorePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePanel1, CTFBaseStorePanel );
+public:
+ CTFStorePanel1( vgui::Panel *parent );
+
+ // UI Layout
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnThink();
+
+ // GC Management
+ virtual void PostTransactionCompleted( void );
+
+private:
+ virtual CStorePage *CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+};
+
+#endif // TF_STORE_PANEL1_H
diff --git a/game/client/tf/vgui/store/v1/tf_store_preview_item.cpp b/game/client/tf/vgui/store/v1/tf_store_preview_item.cpp
new file mode 100644
index 0000000..7f23513
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_preview_item.cpp
@@ -0,0 +1,100 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v1/tf_store_preview_item.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePreviewItemPanel1::CTFStorePreviewItemPanel1( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner )
+: BaseClass( pParent, pResFile, "storepreviewitem", pOwner )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::OnCommand( const char *command )
+{
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::OnClassIconSelected( KeyValues *data )
+{
+ BaseClass::OnClassIconSelected( data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::OnHideClassIconMouseover( void )
+{
+ BaseClass::OnHideClassIconMouseover();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::OnShowClassIconMouseover( KeyValues *data )
+{
+ BaseClass::OnShowClassIconMouseover( data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry )
+{
+ BaseClass::PreviewItem( iClass, pItem, pEntry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::SetState( preview_state_t iState )
+{
+ BaseClass::SetState( iState );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::UpdateIcons( void )
+{
+ BaseClass::UpdateIcons();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel1::OnTick( void )
+{
+ BaseClass::OnTick();
+}
diff --git a/game/client/tf/vgui/store/v1/tf_store_preview_item.h b/game/client/tf/vgui/store/v1/tf_store_preview_item.h
new file mode 100644
index 0000000..02994c4
--- /dev/null
+++ b/game/client/tf/vgui/store/v1/tf_store_preview_item.h
@@ -0,0 +1,41 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PREVIEW_ITEM1_H
+#define TF_STORE_PREVIEW_ITEM1_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/tf_store_preview_item_base.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePreviewItemPanel1 : public CTFStorePreviewItemPanelBase
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePreviewItemPanel1, CTFStorePreviewItemPanelBase );
+public:
+ CTFStorePreviewItemPanel1( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void OnTick( void );
+
+ virtual void PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL ) OVERRIDE;
+ virtual void SetState( preview_state_t iState );
+
+ MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data );
+ MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" );
+ MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data );
+
+private:
+ virtual void UpdateIcons( void );
+};
+
+#endif // TF_STORE_PREVIEW_ITEM1_H
diff --git a/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.cpp b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.cpp
new file mode 100644
index 0000000..1ee55a8
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.cpp
@@ -0,0 +1,83 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v2/tf_store_mapstamps_info_dialog.h"
+#include "tf_mouseforwardingpanel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+DECLARE_BUILD_FACTORY( CTFMapStampsInfoDialog );
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMapStampsInfoDialog::CTFMapStampsInfoDialog( vgui::Panel *pParent, const char *pName )
+: BaseClass( pParent, "MapStampsInfoDialog" )
+{
+ m_pBgPanel = new CMouseMessageForwardingPanel( this, "BgPanel" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapStampsInfoDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/econ/store/v2/StoreMapStampsInfoDialog.res" );
+
+ m_pDlgFrame = dynamic_cast<EditablePanel *>( FindChildByName( "DialogFrame" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapStampsInfoDialog::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapStampsInfoDialog::OnCommand( const char *command )
+{
+ if ( !V_strnicmp( command, "close", 5 ) )
+ {
+ DoClose();
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapStampsInfoDialog::OnMouseReleased(MouseCode code)
+{
+ BaseClass::OnMouseReleased( code );
+
+ if ( m_pDlgFrame && !m_pDlgFrame->IsCursorOver() )
+ {
+ DoClose();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapStampsInfoDialog::DoClose()
+{
+ SetVisible( false );
+ MarkForDeletion();
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.h b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.h
new file mode 100644
index 0000000..7486e8c
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_mapstamps_info_dialog.h
@@ -0,0 +1,36 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_MAPS_INFO_DIALOG_H
+#define TF_MAPS_INFO_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFMapStampsInfoDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFMapStampsInfoDialog, vgui::EditablePanel );
+public:
+ CTFMapStampsInfoDialog( vgui::Panel *pParent, const char *pName = "" );
+
+ void DoClose();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void OnMouseReleased(vgui::MouseCode code);
+
+ Panel *m_pBgPanel;
+ EditablePanel *m_pDlgFrame;
+};
+
+#endif // TF_MAPS_INFO_DIALOG_H
diff --git a/game/client/tf/vgui/store/v2/tf_store_page2.cpp b/game/client/tf/vgui/store/v2/tf_store_page2.cpp
new file mode 100644
index 0000000..c7ac2f2
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_page2.cpp
@@ -0,0 +1,849 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v2/tf_store_page2.h"
+#include "store/v2/tf_store_preview_item2.h"
+#include "c_tf_freeaccount.h"
+#include "store/store_panel.h"
+#include "store/tf_store.h"
+#include "navigationpanel.h"
+#include "econ/store/store_page_new.h"
+#include "econ_item_system.h"
+#include "c_tf_gamestats.h"
+#include "vgui_controls/TextImage.h"
+#include "econ_item_description.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+static const int kNumSortTypes = 5;
+ItemSortTypeData_t g_StoreSortTypes[ kNumSortTypes ] =
+{
+ { "#Store_SortType_DateNewest", kEconStoreSortType_DateNewest },
+ { "#Store_SortType_DateOldest", kEconStoreSortType_DateOldest },
+ { "#Store_SortType_HighestPrice", kEconStoreSortType_Price_HighestToLowest },
+ { "#Store_SortType_LowestPrice", kEconStoreSortType_Price_LowestToHighest },
+ { "#Store_SortType_Alphabetical", kEconStoreSortType_Name_AToZ },
+};
+
+class CClassFilterTooltip : public vgui::BaseTooltip
+{
+public:
+ CClassFilterTooltip( CTFStorePage2 *pStorePage )
+ : BaseTooltip( pStorePage )
+ , m_pStorePage( pStorePage )
+ {
+ }
+
+ CTFStorePage2 *m_pStorePage;
+
+ virtual void SetText(const char *text)
+ {
+ m_pStorePage->m_pClassFilterTooltipLabel->SetText( text );
+ }
+
+ virtual void ShowTooltip(Panel *currentPanel)
+ {
+ int x = 0;
+ int y = currentPanel->GetTall();
+ currentPanel->LocalToScreen( x, y );
+ m_pStorePage->ScreenToLocal( x, y );
+
+ // The tooltip wants to be centered around the given panel, but it's constrained by the left and right boundaries
+ // of the navigation panel.
+ if ( m_pStorePage->m_pClassFilterButtons )
+ {
+ // Right side of tooltip should not pass right boundary of nav panel
+ int aClassFilterNavPos[2] = { 0, 0 };
+ m_pStorePage->m_pClassFilterButtons->GetPos( aClassFilterNavPos[0], aClassFilterNavPos[1] );
+ const int nTipWide = m_pStorePage->m_pClassFilterTooltipLabel->GetWide();
+ x = clamp(
+ x + ( currentPanel->GetWide() - nTipWide ) / 2,
+ aClassFilterNavPos[0],
+ aClassFilterNavPos[0] + m_pStorePage->m_pClassFilterButtons->GetWide() - nTipWide );
+ }
+
+ m_pStorePage->m_pClassFilterTooltipLabel->SetPos( x, y );
+ m_pStorePage->m_pClassFilterTooltipLabel->SetVisible( true );
+ }
+ virtual void HideTooltip()
+ {
+ m_pStorePage->m_pClassFilterTooltipLabel->SetVisible( false );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage2::CTFStorePage2(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile )
+: BaseClass( parent, pPageData, pPreviewItemResFile ),
+ m_pSubcategoriesFilterCombo( NULL ),
+ m_pSortByCombo( NULL ),
+ m_pHomeCategoryTabs( NULL ),
+ m_pClassFilterButtons( NULL ),
+ m_pNameFilterTextEntry( NULL ),
+ m_pSubcategoriesFilterLabel( NULL ),
+ m_iCurrentSubcategory( 2 ), // Switch to Featured items tab on home page when store launches; BRETT SAID I COULD DO THIS
+ m_pClassFilterTooltipLabel( NULL ),
+ m_pClassFilterTooltip( NULL ),
+ m_flFilterItemTime( 0.0f )
+{
+ const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet();
+
+ if ( IsHomePage() )
+ {
+ bool bAtLeastOneItemIsOnSale = false;
+ if ( pPriceSheet )
+ {
+ const CEconStorePriceSheet::StoreEntryMap_t& mapStoreEntries = pPriceSheet->GetEntries();
+ FOR_EACH_MAP_FAST( mapStoreEntries, i )
+ {
+ if ( mapStoreEntries[i].IsOnSale( EconUI()->GetStorePanel()->GetCurrency() ) )
+ {
+ bAtLeastOneItemIsOnSale = true;
+ break;
+ }
+ }
+ }
+
+ m_pHomeCategoryTabs = new CNavigationPanel( this, "ItemCategoryTabs" );
+
+ FOR_EACH_VEC( m_pPageData->m_vecSubcategories, i )
+ {
+ // Skip over adding the "On Sale!" tab if no items are currently on sale.
+ const char *pchName = m_pPageData->m_vecSubcategories[i]->m_pchName;
+ bool bIsOnSaleCategory = m_pPageData->m_vecSubcategories[i]->m_unID == CEconStoreCategoryManager::k_CategoryID_OnSale;
+
+ // Skip over the "Top Sellers" tab as we're replacing it with the 'Starter Packs' tab for now
+ bool bIsPopularCategory = m_pPageData->m_vecSubcategories[i]->m_unID == CEconStoreCategoryManager::k_CategoryID_Popular;
+
+ // Skip over the "New" tab as we're replacing it with the 'Featured' tab for now
+ bool bIsNew = m_pPageData->m_vecSubcategories[i]->m_unID == CEconStoreCategoryManager::k_CategoryID_New;
+
+ // We include all of the sale items in the Featured tab currently, so we don't need to add these categories back in at the moment
+ bAtLeastOneItemIsOnSale = false;
+
+ if ( ( !bIsPopularCategory && !bIsOnSaleCategory && !bIsNew ) || ( bAtLeastOneItemIsOnSale && bIsOnSaleCategory ) )
+ {
+ m_pHomeCategoryTabs->AddButton( i, pchName );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+CTFStorePage2::~CTFStorePage2()
+{
+ delete m_pClassFilterTooltip;
+ m_pClassFilterTooltip = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnPostCreate()
+{
+ BaseClass::OnPostCreate();
+
+ m_bShouldDeletePreviewPanel = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFStorePage2::HasSubcategories() const
+{
+ return m_pPageData && m_pPageData->HasSubcategories();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pSubcategoriesFilterCombo )
+ {
+ m_pSubcategoriesFilterCombo->SetVisible( HasSubcategories() );
+ }
+
+ if ( m_pSubcategoriesFilterLabel )
+ {
+ m_pSubcategoriesFilterLabel->SetVisible( HasSubcategories() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pNameFilterTextEntry = FindControl<vgui::TextEntry>( "NameFilterTextEntry" );
+ if ( m_pNameFilterTextEntry )
+ {
+ m_pNameFilterTextEntry->AddActionSignalTarget( this );
+ }
+
+ m_pSortByCombo = dynamic_cast< ComboBox * >( FindChildByName( "SortFilterComboBox" ) );
+ if ( m_pSortByCombo )
+ {
+ m_pSortByCombo->RemoveAll();
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pSortByCombo->SetFont( hFont );
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ for ( int i = 0; i < ARRAYSIZE(g_StoreSortTypes); i++ )
+ {
+ pKeyValues->SetInt( "sortby", i );
+ m_pSortByCombo->AddItem( g_StoreSortTypes[i].szSortDesc, pKeyValues );
+ }
+ pKeyValues->deleteThis();
+ m_pSortByCombo->ActivateItemByRow( 0 );
+ }
+
+ m_pSubcategoriesFilterCombo = dynamic_cast< ComboBox * >( FindChildByName( "SubcategoryFilterComboBox" ) );
+ if ( m_pSubcategoriesFilterCombo )
+ {
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pSubcategoriesFilterCombo->SetFont( hFont );
+
+ m_pSubcategoriesFilterCombo->RemoveAll();
+
+ if ( m_pPageData )
+ {
+ // Add "all items" explicitly
+ KeyValuesAD kvAllItems( "data" );
+ kvAllItems->SetInt( "index", GetNumSubcategories() );
+ m_pSubcategoriesFilterCombo->AddItem( "#Store_ClassFilter_None", kvAllItems );
+
+ FOR_EACH_VEC( m_pPageData->m_vecSubcategories, i )
+ {
+ KeyValues *pData = new KeyValues( "data" );
+ pData->SetInt( "index", i );
+
+ m_pSubcategoriesFilterCombo->AddItem( m_pPageData->m_vecSubcategories[i]->m_pchName, pData );
+
+ pData->deleteThis();
+ }
+ }
+
+ // Move to "All items" selected
+ m_pSubcategoriesFilterCombo->ActivateItemByRow( 0 );
+
+ m_pSubcategoriesFilterCombo->GetComboButton()->SetFgColor( Color( 117,107,94,255 ) );
+ m_pSubcategoriesFilterCombo->GetComboButton()->SetDefaultColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ m_pSubcategoriesFilterCombo->GetComboButton()->SetArmedColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ m_pSubcategoriesFilterCombo->GetComboButton()->SetDepressedColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ }
+
+ m_pClassFilterButtons = dynamic_cast< CNavigationPanel * >( FindChildByName( "ClassFilterNavPanel" ) );
+ m_pSubcategoriesFilterLabel = dynamic_cast< CExLabel * >( FindChildByName( "SubcategoryFiltersLabel" ) );
+
+ m_pClassFilterTooltipLabel = dynamic_cast< CExLabel * >( FindChildByName( "ClassFilterTooltipLabel" ) );
+ if ( m_pClassFilterTooltipLabel && m_pClassFilterButtons )
+ {
+ m_pClassFilterTooltip = new CClassFilterTooltip(this);
+ for ( int i = 0 ; i < m_pClassFilterButtons->NumButtons() ; ++i )
+ {
+ CExButton *pButton = m_pClassFilterButtons->GetButton( i );
+ CUtlString sSaveText = pButton->GetEffectiveTooltipText();
+ pButton->SetTooltip( m_pClassFilterTooltip, sSaveText );
+ }
+ }
+
+ // Setup title text in home page
+ if ( IsHomePage() && g_pVGuiLocalize )
+ {
+ CExLabel *pTitleLabel = dynamic_cast<CExLabel *>( FindChildByName( "TitleLabel" ) );
+ wchar_t *pHomePageTitle = g_pVGuiLocalize->Find( "#Store_HomePageTitle" );
+ wchar_t *pRedText = g_pVGuiLocalize->Find( "#Store_HomePageTitleRedText" );
+
+ if ( pTitleLabel && pHomePageTitle && pRedText )
+ {
+ const store_promotion_spend_for_free_item_t *pPromotion = EconUI()->GetStorePanel()->GetPriceSheet()->GetStorePromotion_SpendForFreeItem();
+
+ ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ AssertMsg( eCurrency >= k_ECurrencyUSD && eCurrency < k_ECurrencyMax, "Invalid currency!" );
+ int iPriceThreshold = pPromotion->m_rgusPriceThreshold[ eCurrency ];
+ wchar_t wszPriceThreshold[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wszPriceThreshold, ARRAYSIZE( wszPriceThreshold ), iPriceThreshold, EconUI()->GetStorePanel()->GetCurrency() );
+
+ static wchar_t wszText[512];
+ g_pVGuiLocalize->ConstructString_safe( wszText, pHomePageTitle, 2, pRedText, wszPriceThreshold );
+
+ pTitleLabel->SetText( wszText );
+ TextImage *pTextImage = pTitleLabel->GetTextImage();
+ const wchar_t *pFound = wcsstr( wszText, pRedText );
+ if ( pTextImage && pFound )
+ {
+ const int iRedTextPos = pFound - wszText;
+ const int nRedTextLen = wcslen( pRedText );
+ pTextImage->ClearColorChangeStream();
+ pTextImage->AddColorChange( Color(200,80,60,255), iRedTextPos );
+ pTextImage->AddColorChange( pTitleLabel->GetFgColor(), iRedTextPos + nRedTextLen );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFStorePage2::GetPageResFile( void )
+{
+ if ( IsHomePage() )
+ {
+ Assert( ShouldUseNewStore() );
+
+ return "Resource/UI/econ/store/v2/StoreHome_Premium.res";
+ }
+
+ return m_pPageData->m_pchPageRes;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnPageShow( void )
+{
+ BaseClass::OnPageShow();
+
+ if ( m_pClassFilterButtons )
+ {
+ m_pClassFilterButtons->UpdateButtonSelectionStates( 0 );
+ }
+
+ ClearNameFilter( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnCommand( const char *command )
+{
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnItemDetails( vgui::Panel *panel )
+{
+ CStoreItemControlsPanel *pControlsPanel = dynamic_cast< CStoreItemControlsPanel * >( panel );
+ if ( pControlsPanel )
+ {
+ const econ_store_entry_t *pEntry = pControlsPanel->GetItem();
+ if ( pEntry && m_pPreviewPanel )
+ {
+ ShowPreviewWindow( pEntry->GetItemDefinitionIndex() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnItemDefDetails( KeyValues *pData )
+{
+ ShowPreviewWindow( pData->GetInt("ItemDefIndex", -1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::ShowPreviewWindow( item_definition_index_t usDefIndex )
+{
+ if ( m_pPreviewPanel )
+ {
+ CEconItemView itemData;
+ itemData.Init( usDefIndex, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ itemData.SetClientItemFlags( kEconItemFlagClient_Preview );
+
+ m_pPreviewPanel->PreviewItem( 0, &itemData );
+ m_pPreviewPanel->SetState( PS_ITEM ); // Adding this, since without it, only the item icon shows up
+ m_pPreviewPanel->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnNavButtonSelected( KeyValues *pData )
+{
+ Panel *pPanel = (Panel *)pData->GetPtr( "panel" );
+
+ if ( pPanel == m_pClassFilterButtons )
+ {
+ const int iFilter = pData->GetInt( "userdata", -1 ); AssertMsg( iFilter >= 0, "Bad filter" );
+ if ( iFilter < 0 )
+ return;
+
+ SetFilter( iFilter );
+ m_iCurrentPage = 0;
+ UpdateModelPanels();
+
+ if ( m_pCheckoutButton )
+ {
+ m_pCheckoutButton->RequestFocus();
+ }
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(class)", CFmtStr( "%i", iFilter ).Access() );
+ }
+ else if ( pPanel == m_pHomeCategoryTabs )
+ {
+ int iSelectedTab = pData->GetInt( "userdata", -1 );
+ if ( iSelectedTab < 0 )
+ return;
+
+ if ( !m_pPageData || !m_pPageData->m_vecSubcategories.IsValidIndex( iSelectedTab ) )
+ return;
+
+ m_iCurrentSubcategory = iSelectedTab;
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ // Delete the old one
+ m_vecItemPanels[i].m_pStorePricePanel->MarkForDeletion();
+
+ // Create a new one and cache it
+ CStorePricePanel *pPricePanel = CreatePricePanel( i );
+ pPricePanel->InvalidateLayout();
+ pPricePanel->SetMouseInputEnabled( false );
+ pPricePanel->SetKeyBoardInputEnabled( false );
+
+ m_vecItemPanels[i].m_pStorePricePanel = pPricePanel;
+
+ // Setup the mouse handler
+ m_vecItemPanels[i].m_pItemControlsPanel->SetMouseHoverHandler( pPricePanel );
+ }
+
+ m_iCurrentPage = 0;
+
+ UpdateFilteredItems();
+ UpdateModelPanels();
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(category)", CFmtStr( "%i", iSelectedTab ).Access() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+
+ vgui::TextEntry *pTextEntry = dynamic_cast<vgui::TextEntry *>( pPanel );
+
+ if ( pTextEntry )
+ {
+ if ( pTextEntry == m_pNameFilterTextEntry )
+ {
+ m_wNameFilter.RemoveAll();
+ if ( m_pNameFilterTextEntry->GetTextLength() )
+ {
+ m_wNameFilter.EnsureCount( m_pNameFilterTextEntry->GetTextLength() + 1 );
+ m_pNameFilterTextEntry->GetText( m_wNameFilter.Base(), m_wNameFilter.Count() * sizeof(wchar_t) );
+ V_wcslower( m_wNameFilter.Base() );
+ }
+ m_flFilterItemTime = gpGlobals->curtime + 0.5f;
+ return;
+ }
+ }
+
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+
+ if ( pComboBox )
+ {
+ if ( pComboBox == m_pSubcategoriesFilterCombo )
+ {
+ // the class selection combo box changed, update class details
+ KeyValues *pUserData = m_pSubcategoriesFilterCombo->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ // Update current subcategory filter
+ m_iCurrentSubcategory = pUserData->GetInt( "index", 0 );
+ m_bFilterDirty = true;
+
+ m_iCurrentPage = 0;
+ UpdateModelPanels();
+
+ if ( m_pCheckoutButton )
+ {
+ m_pCheckoutButton->RequestFocus();
+ }
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(subcategories)", CFmtStr( "%i", m_iCurrentSubcategory ).Access() );
+ }
+
+ else if ( pComboBox == m_pSortByCombo )
+ {
+ // the class selection combo box changed, update class details
+ KeyValues *pUserData = m_pSortByCombo->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ int iSortTypeSelectionIndex = pUserData->GetInt( "sortby", -1 );
+ m_bFilterDirty = true;
+ if ( iSortTypeSelectionIndex >= 0 )
+ {
+ eEconStoreSortType iSortType = (eEconStoreSortType)g_StoreSortTypes[iSortTypeSelectionIndex].iSortType;
+
+ UpdateFilteredItems();
+ UpdateModelPanels();
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_page_2_nav(sort_by)", CFmtStr( "%i", iSortType ).Access() );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters )
+{
+ BaseClass::GetFiltersForDef( pDef, pVecFilters );
+}
+
+bool CTFStorePage2::FindAndSelectEntry( const econ_store_entry_t *pEntry )
+{
+ m_iCurrentSubcategory = GetAllSubcategoriesIndex();
+ m_bFilterDirty = true;
+ if ( BaseClass::FindAndSelectEntry( pEntry ) )
+ {
+ ivgui()->PostMessage( GetVPanel(), new KeyValues( "ItemDefDetails", "ItemDefIndex", pEntry->GetItemDefinitionIndex() ), GetVPanel() );
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::ClearNameFilter( bool bUpdateModelPanels )
+{
+ // don't do anything if we don't have any filter
+ if ( m_wNameFilter.Count() == 0 )
+ return;
+
+ m_wNameFilter.RemoveAll();
+ if( m_pNameFilterTextEntry )
+ {
+ m_pNameFilterTextEntry->SetText( "" );
+ }
+
+ if ( bUpdateModelPanels )
+ {
+ m_flFilterItemTime = gpGlobals->curtime + 0.1f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFStorePage2::GetAllSubcategoriesIndex() const
+{
+ return m_pPageData ? m_pPageData->GetNumSubcategories() : 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::UpdateFilteredItems()
+{
+ if ( m_bFilterDirty )
+ {
+ // Make sure list of unfiltered items is sorted
+ m_pSortByCombo = dynamic_cast< ComboBox * >( FindChildByName( "SortFilterComboBox" ) );
+ if ( m_pSortByCombo )
+ {
+ KeyValues *pUserData = m_pSortByCombo->GetActiveItemUserData();
+ if ( pUserData )
+ {
+ int iSortTypeSelectionIndex = pUserData->GetInt( "sortby", -1 );
+ if ( iSortTypeSelectionIndex >= 0 )
+ {
+ eEconStoreSortType iSortType = (eEconStoreSortType)g_StoreSortTypes[iSortTypeSelectionIndex].iSortType;
+ CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheetForEdit();
+ pPriceSheet->SetEconStoreSortType( iSortType );
+
+ CEconStoreCategoryManager::StoreCategory_t *pPageData = const_cast< CEconStoreCategoryManager::StoreCategory_t * >( m_pPageData );
+ pPageData->m_vecEntries.SetLessContext( pPriceSheet );
+ pPageData->m_vecEntries.RedoSort( true );
+ }
+ }
+ }
+ }
+
+ if ( !IsHomePage() )
+ {
+ BaseClass::UpdateFilteredItems();
+ return;
+ }
+
+ m_FilteredEntries.Purge();
+
+ // Subcategories on the home page are special cases, as they aren't based on
+ // an item's tags.
+ const StoreCategoryID_t unSubcategoryID = m_pPageData->m_vecSubcategories[ m_iCurrentSubcategory ]->m_unID;
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( !pStorePanel )
+ return;
+
+ FOR_EACH_VEC( m_vecItemPanels, idx )
+ {
+ m_vecItemPanels[idx].m_pItemModelPanel->SetShowQuantity( unSubcategoryID != CEconStoreCategoryManager::k_CategoryID_Popular );
+ }
+
+ if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Popular )
+ {
+ const CUtlVector<uint32>& popularItems = pStorePanel->GetPopularItems();
+ for ( int i = 0; i < MIN( m_vecItemPanels.Count(), popularItems.Count() ); ++i )
+ {
+ const econ_store_entry_t *pEntry = pStorePanel->GetPriceSheet()->GetEntry( popularItems[i] );
+ m_FilteredEntries.AddToTail( pEntry );
+ }
+ }
+ else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_New )
+ {
+ // Add all new items
+ const CUtlMap< uint16, econ_store_entry_t > &mapEntries = pStorePanel->GetPriceSheet()->GetEntries();
+ FOR_EACH_MAP_FAST( mapEntries, i )
+ {
+ const econ_store_entry_t *pCurEntry = &mapEntries[i];
+ if ( pCurEntry->m_bNew )
+ {
+ m_FilteredEntries.AddToTail( pCurEntry );
+ }
+ }
+ }
+ else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_OnSale )
+ {
+ ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ // Add all entries that are on sale
+ const CEconStorePriceSheet::StoreEntryMap_t &mapEntries = pStorePanel->GetPriceSheet()->GetEntries();
+ FOR_EACH_MAP_FAST( mapEntries, i )
+ {
+ const econ_store_entry_t *pCurEntry = &mapEntries[i];
+
+ if ( pCurEntry->IsOnSale( eCurrency ) )
+ {
+ m_FilteredEntries.AddToTail( pCurEntry );
+ }
+ }
+ }
+ else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Featured )
+ {
+ const CEconStorePriceSheet::FeaturedItems_t& vecFeaturedItems = pStorePanel->GetPriceSheet()->GetFeaturedItems();
+ FOR_EACH_VEC( vecFeaturedItems, i )
+ {
+ const econ_store_entry_t *pEntry = pStorePanel->GetPriceSheet()->GetEntry( vecFeaturedItems[i] );
+ if ( pEntry )
+ {
+ m_FilteredEntries.AddToTail( pEntry );
+ }
+ else
+ {
+ AssertMsg( 0, "trying to add featured item that's not in the store price sheet.\n" );
+ }
+ }
+ }
+ else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_ClassBundles )
+ {
+ // Let's find the class bundles
+ const CEconStorePriceSheet::StoreEntryMap_t &mapEntries = pStorePanel->GetPriceSheet()->GetEntries();
+ FOR_EACH_MAP_FAST( mapEntries, i )
+ {
+ const econ_store_entry_t *pCurEntry = &mapEntries[i];
+
+ if ( pCurEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_ClassBundles ) )
+ {
+ m_FilteredEntries.AddToTail( pCurEntry );
+ }
+ }
+
+ // Sort by date to get the weapon bundles before the keyless crates
+ //extern int ItemNameSortComparator( const econ_store_entry_t *const *ppEntryA, const econ_store_entry_t *const *ppEntryB );
+ extern int FirstSaleDateSortComparator( const econ_store_entry_t *const *ppItemA, const econ_store_entry_t *const *ppItemB );
+
+ m_FilteredEntries.Sort( &FirstSaleDateSortComparator );
+
+ }
+ else if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Taunts )
+ {
+ const CEconStorePriceSheet::StoreEntryMap_t &mapEntries = pStorePanel->GetPriceSheet()->GetEntries();
+ FOR_EACH_MAP_FAST( mapEntries, i )
+ {
+ const econ_store_entry_t *pCurEntry = &mapEntries[i];
+
+ if ( pCurEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Taunts ) )
+ {
+ m_FilteredEntries.AddToTail( pCurEntry );
+ }
+ }
+ }
+ else
+ {
+ AssertMsg( 0, "Subcategory has no defined behavior in code" );
+ }
+
+ // If we're either "New" category or the "On Sale" category or the "Taunts" category, sort our contents
+ // by sale date.
+ if ( unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_New || unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_OnSale || unSubcategoryID == CEconStoreCategoryManager::k_CategoryID_Taunts )
+ {
+ extern int FirstSaleDateSortComparator( const econ_store_entry_t *const *ppItemA, const econ_store_entry_t *const *ppItemB );
+
+ m_FilteredEntries.Sort( &FirstSaleDateSortComparator );
+ }
+
+ m_bFilterDirty = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFStorePage2::DoesEntryFilterPassSecondaryFilter( const econ_store_entry_t *pEntry )
+{
+ if ( !DoesEntryFilterPassSubcategoryFilter( pEntry ) )
+ {
+ return false;
+ }
+
+ if ( m_wNameFilter.Count() > 0 )
+ {
+ CEconItemView itemData;
+ itemData.Init( pEntry->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ itemData.SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem );
+ return DoesItemPassSearchFilter( itemData.GetDescription(), m_wNameFilter.Base() );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFStorePage2::DoesEntryFilterPassSubcategoryFilter( const econ_store_entry_t *pEntry )
+{
+ Assert( pEntry );
+ Assert( m_pPageData );
+
+ // Make sure pages without subcategories can still function
+ if ( !HasSubcategories() )
+ return true;
+
+ // "All subcategories" item selected?
+ if ( m_iCurrentSubcategory == GetAllSubcategoriesIndex() )
+ return true;
+
+ if ( !m_pPageData->m_vecSubcategories.IsValidIndex( m_iCurrentSubcategory ) )
+ return false;
+
+ // Get the subcategory ID
+ const StoreCategoryID_t unSubCategoryID = m_pPageData->m_vecSubcategories[ m_iCurrentSubcategory ]->m_unID;
+
+ // If the store entry is covered by the currently selected category, return true.
+// return pEntry->m_vecTagIds.Find( unSubCategoryID ) != pEntry->m_vecTagIds.InvalidIndex();
+ return pEntry->IsListedInCategory( unSubCategoryID );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::UpdateFilterComboBox( void )
+{
+ BaseClass::UpdateFilterComboBox();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnThink( void )
+{
+ BaseClass::OnThink();
+
+ if ( m_flFilterItemTime && gpGlobals->curtime >= m_flFilterItemTime )
+ {
+ m_bFilterDirty = true;
+ UpdateFilteredItems();
+ UpdateModelPanels();
+ m_flFilterItemTime = 0.0f;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel *CTFStorePage2::CreatePreviewPanel( void )
+{
+ return new CTFStorePreviewItemPanel2( EconUI()->GetStorePanel(), m_pPreviewItemResFile, "storepreviewitem", this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel* CTFStorePage2::CreatePricePanel( int iIndex )
+{
+ if ( m_pPageData &&
+ m_pPageData->m_bIsHome &&
+ HasSubcategories() &&
+ m_pPageData->m_vecSubcategories.IsValidIndex( m_iCurrentSubcategory ) &&
+ m_pPageData->m_vecSubcategories[ m_iCurrentSubcategory ]->m_unID == CEconStoreCategoryManager::k_CategoryID_Popular )
+ {
+ return vgui::SETUP_PANEL( new CStorePricePanel_Popular( this, "StorePrice", iIndex + 1 ) );
+ }
+
+ return BaseClass::CreatePricePanel( iIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnAddItemToCart( KeyValues *pData )
+{
+ item_definition_index_t iItemDef = (item_definition_index_t)pData->GetInt( "item_def", INVALID_ITEM_DEF_INDEX );
+
+ AddItemToCartHelper( GetPageName(), iItemDef, (ECartItemType)pData->GetInt( "cart_add_type", kCartItem_Purchase ) );
+ UpdateCart();
+
+ // Turn the free slots indicator red if we can't fit everything.
+ UpdateBackpackLabel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ if ( pItemPanel && IsVisible() && pItemPanel->HasItem() )
+ {
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i].m_pItemModelPanel == pItemPanel )
+ {
+ ShowPreviewWindow( m_vecItemPanels[i].m_pItemModelPanel->GetItem()->GetItemDefIndex() );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage2::OnItemPanelMouseDoublePressed( vgui::Panel *panel )
+{
+ // Do nothing
+}
diff --git a/game/client/tf/vgui/store/v2/tf_store_page2.h b/game/client/tf/vgui/store/v2/tf_store_page2.h
new file mode 100644
index 0000000..8c25c92
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_page2.h
@@ -0,0 +1,82 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PAGE2_H
+#define TF_STORE_PAGE2_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/tf_store_page_base.h"
+
+class CNavigationPanel;
+class CClassFilterTooltip;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage2 : public CTFStorePageBase
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage2, CTFStorePageBase );
+public:
+ CTFStorePage2( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL );
+ ~CTFStorePage2();
+
+ virtual void OnPostCreate();
+
+ bool HasSubcategories() const;
+ int GetNumSubcategories() const { return m_pPageData ? m_pPageData->m_vecSubcategories.Count() : 0; }
+
+ virtual void PerformLayout();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ virtual const char *GetPageResFile( void );
+ virtual void OnCommand( const char *command );
+
+ MESSAGE_FUNC( OnPageShow, "PageShow" );
+ MESSAGE_FUNC_PTR( OnItemDetails, "ItemDetails", panel );
+ MESSAGE_FUNC_PARAMS( OnItemDefDetails, "ItemDefDetails", pData );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", pData );
+ MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData );
+ MESSAGE_FUNC_PARAMS( OnAddItemToCart, "AddItemToCart", data ); // Comes from preview panel
+ MESSAGE_FUNC_PTR( OnItemPanelMouseDoublePressed, "ItemPanelMouseDoublePressed", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel ); // Comes from CStoreItemControlsPanel
+
+ virtual bool DoesEntryFilterPassSecondaryFilter( const econ_store_entry_t *pEntry );
+ bool DoesEntryFilterPassSubcategoryFilter( const econ_store_entry_t *pEntry );
+
+ virtual void UpdateFilteredItems( void );
+ virtual void UpdateFilterComboBox( void );
+ virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters );
+ virtual void OnThink( void );
+ virtual bool FindAndSelectEntry( const econ_store_entry_t *pEntry );
+
+ void ClearNameFilter( bool bUpdateModelPanels );
+
+ virtual CStorePreviewItemPanel *CreatePreviewPanel( void );
+ virtual CStorePricePanel* CreatePricePanel( int iIndex );
+
+ void ShowPreviewWindow( item_definition_index_t usDefIndex );
+ int GetAllSubcategoriesIndex() const;
+
+ vgui::TextEntry *m_pNameFilterTextEntry;
+ CExLabel *m_pSubcategoriesFilterLabel;
+ vgui::ComboBox *m_pSubcategoriesFilterCombo;
+ vgui::ComboBox *m_pSortByCombo;
+ CNavigationPanel *m_pHomeCategoryTabs;
+ CNavigationPanel *m_pClassFilterButtons;
+ CExLabel *m_pClassFilterTooltipLabel;
+ CClassFilterTooltip *m_pClassFilterTooltip;
+
+ int m_iCurrentSubcategory;
+ CUtlVector<wchar_t> m_wNameFilter;
+ float m_flFilterItemTime;
+
+ friend class CClassFilterTooltip;
+};
+
+#endif // TF_STORE_PAGE2_H
diff --git a/game/client/tf/vgui/store/v2/tf_store_page_maps2.cpp b/game/client/tf/vgui/store/v2/tf_store_page_maps2.cpp
new file mode 100644
index 0000000..3fb0ccd
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_page_maps2.cpp
@@ -0,0 +1,59 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v2/tf_store_page_maps2.h"
+#include "store/v2/tf_store_mapstamps_info_dialog.h"
+#include "store/store_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage_Maps2::CTFStorePage_Maps2( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData )
+: BaseClass( parent, pPageData, "Resource/UI/econ/store/v2/StorePreviewItemPanel_Maps.res" )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage_Maps2::OnPageShow()
+{
+ BaseClass::OnPageShow();
+
+ SetDetailsVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage_Maps2::OnCommand( const char *command )
+{
+ if ( !V_strnicmp( command, "maps_learnmore", 14 ) )
+ {
+ DisplayMapStampsDialog();
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePage_Maps2::DisplayMapStampsDialog()
+{
+ CTFMapStampsInfoDialog *pDlg = vgui::SETUP_PANEL( new CTFMapStampsInfoDialog( EconUI()->GetStorePanel() ) );
+ pDlg->SetVisible( true );
+ pDlg->InvalidateLayout( true, true );
+ pDlg->SetKeyBoardInputEnabled(true);
+ pDlg->SetMouseInputEnabled(true);
+}
diff --git a/game/client/tf/vgui/store/v2/tf_store_page_maps2.h b/game/client/tf/vgui/store/v2/tf_store_page_maps2.h
new file mode 100644
index 0000000..567ea5d
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_page_maps2.h
@@ -0,0 +1,35 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_MAPS2_H
+#define STORE_PAGE_MAPS2_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/v2/tf_store_page2.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage_Maps2 : public CTFStorePage2
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage_Maps2, CTFStorePage2 );
+public:
+ CTFStorePage_Maps2( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+ virtual ~CTFStorePage_Maps2() {}
+
+ virtual const char* GetPageResFile() { return "Resource/UI/econ/store/v2/StorePage_Maps.res"; }
+
+protected:
+ virtual void OnCommand( const char *command );
+ virtual void OnPageShow( void );
+
+ void DisplayMapStampsDialog();
+};
+
+#endif // STORE_PAGE_MAPS2_H
diff --git a/game/client/tf/vgui/store/v2/tf_store_panel2.cpp b/game/client/tf/vgui/store/v2/tf_store_panel2.cpp
new file mode 100644
index 0000000..89fa857
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_panel2.cpp
@@ -0,0 +1,109 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v2/tf_store_panel2.h"
+#include "store/v2/tf_store_page2.h"
+#include "store/v2/tf_store_page_maps2.h"
+#include "store/store_page_halloween.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePanel2::CTFStorePanel2( vgui::Panel *parent ) : CTFBaseStorePanel(parent)
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePanel2::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePanel2::ShowPanel( bool bShow )
+{
+ BaseClass::ShowPanel( bShow );
+
+ // Base class should turn this on
+ if ( bShow && m_bOGSLogging )
+ {
+ EconUI()->Gamestats_Store( IE_STORE2_ENTERED );
+ }
+
+ Panel *pCheckOutButton = FindChildByName( "CheckOutButton" );
+ if ( pCheckOutButton )
+ {
+ pCheckOutButton->RequestFocus();
+ }
+}
+
+void CTFStorePanel2::OnAddToCart( void )
+{
+ Panel *pCheckOutButton = FindChildByName( "CheckOutButton" );
+ if ( pCheckOutButton )
+ {
+ pCheckOutButton->RequestFocus();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePanel2::OnThink()
+{
+ BaseClass::OnThink();
+}
+
+void CTFStorePanel2::OnKeyCodePressed( vgui::KeyCode code )
+{
+ // ESC cancels
+ if ( code == KEY_XBUTTON_B )
+ {
+ OnCommand( "close" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePanel2::PostTransactionCompleted( void )
+{
+ BaseClass::PostTransactionCompleted();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static store page factory.
+//-----------------------------------------------------------------------------
+CStorePage *CTFStorePanel2::CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData )
+{
+ if ( pPageData )
+ {
+ if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_SpecialPromo" ) )
+ return new CTFStorePage_SpecialPromo( this, pPageData );
+
+ if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Maps" ) )
+ return new CTFStorePage_Maps2( this, pPageData );
+
+ if ( !Q_strcmp( pPageData->m_pchPageClass, "CStorePage_Popular" ) )
+ return new CTFStorePage_Popular( this, pPageData );
+ }
+
+ // Default, standard store page.
+ return new CTFStorePage2( this, pPageData );
+}
diff --git a/game/client/tf/vgui/store/v2/tf_store_panel2.h b/game/client/tf/vgui/store/v2/tf_store_panel2.h
new file mode 100644
index 0000000..4364706
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_panel2.h
@@ -0,0 +1,41 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PANEL2_H
+#define TF_STORE_PANEL2_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/tf_store_panel_base.h"
+
+class CStorePage;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePanel2 : public CTFBaseStorePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePanel2, CTFBaseStorePanel );
+public:
+ CTFStorePanel2( vgui::Panel *parent );
+
+ // UI Layout
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnThink();
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+ virtual void ShowPanel( bool bShow );
+ virtual void OnAddToCart( void );
+
+ // GC Management
+ virtual void PostTransactionCompleted( void );
+
+private:
+ virtual CStorePage *CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+};
+
+#endif // TF_STORE_PANEL2_H
diff --git a/game/client/tf/vgui/store/v2/tf_store_preview_item2.cpp b/game/client/tf/vgui/store/v2/tf_store_preview_item2.cpp
new file mode 100644
index 0000000..c213e10
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_preview_item2.cpp
@@ -0,0 +1,1367 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/v2/tf_store_preview_item2.h"
+#include "econ_item_description.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/ScrollBar.h"
+#include "vgui_controls/ScrollBarSlider.h"
+#include "vgui/IInput.h"
+#include "tf_item_schema.h"
+#include "econ_item_system.h"
+#include "store/store_panel.h"
+#include "c_tf_gamestats.h"
+#include "tf_playermodelpanel.h"
+#include "navigationpanel.h"
+#include "tf_mouseforwardingpanel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+inline float LerpScale( float flIn, float flInMin, float flInMax, float flOutMin, float flOutMax )
+{
+ float flDenom = flInMax - flInMin;
+ if ( flDenom == 0.0f )
+ return 0.0f;
+
+ float t = clamp( ( flIn - flInMin ) / flDenom, 0.0f, 1.0f );
+ return Lerp( t, flOutMin, flOutMax );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+inline float SCurve( float t )
+{
+ t = clamp( t, 0.0f, 1.0f );
+ return t * t * (3 - 2*t);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFullscreenStorePreviewItem::CFullscreenStorePreviewItem( vgui::Panel *pParent, EditablePanel *pOwner )
+: BaseClass( pParent, "FullscreenStorePreview" ),
+ m_iItemDef( INVALID_ITEM_ID ),
+ m_pCycleTextLabel( NULL ),
+ m_pTeamNavPanel( NULL ),
+ m_pPreviewButton( NULL ),
+ m_pRotLeftButton( NULL ),
+ m_pRotRightButton( NULL ),
+ m_pZoomButton( NULL ),
+ m_pOverlayPanel( NULL ),
+ m_flGoFullscreenStartTime( 0.0f ),
+ m_flLastMouseMoveTime( 0.0f ),
+ m_bIsHalloweenOrFullmoonOnlyItem( false )
+{
+ m_hOwner = pOwner;
+}
+
+void CFullscreenStorePreviewItem::SetItemDef( itemid_t iItemDef )
+{
+ m_iItemDef = iItemDef;
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( pStorePanel )
+ {
+ const CEconStorePriceSheet *pPriceSheet = pStorePanel->GetPriceSheet();
+ CExButton *pPreviewButton = dynamic_cast<CExButton *>( FindChildByName( "TryItOutButton" ) );
+ if ( pPriceSheet && pPreviewButton )
+ {
+ const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( m_iItemDef );
+ pPreviewButton->SetVisible( pStoreEntry && pStoreEntry->CanPreview() );
+ }
+ }
+}
+
+void CFullscreenStorePreviewItem::GoFullscreen( CTFPlayerModelPanel *pPlayerModelPanel )
+{
+ m_Stats.Clear();
+
+ m_flGoFullscreenStartTime = gpGlobals->realtime;
+
+ SetAlpha( 0 );
+ SetVisible( true );
+ MoveToFront();
+
+ m_pPlayerModelPanel = pPlayerModelPanel;
+
+ if ( !m_pPlayerModelPanel.Get() )
+ return;
+
+ // Cache old player model panel bounds and such
+ m_pPlayerModelPanel->GetBounds( m_OldModelState.m_aPlayerModelPanelBounds[0], m_OldModelState.m_aPlayerModelPanelBounds[1], m_OldModelState.m_aPlayerModelPanelBounds[2], m_OldModelState.m_aPlayerModelPanelBounds[3] );
+ m_OldModelState.m_vecPlayerPos = m_pPlayerModelPanel->m_vecPlayerPos;
+ m_OldModelState.m_bZoomed = m_pPlayerModelPanel->IsZoomed();
+
+ // Fullscreen panel is new parent
+ m_pPlayerModelPanel->SetParent( this );
+
+ // Get team state
+ if ( m_pTeamNavPanel )
+ {
+ m_pTeamNavPanel->UpdateButtonSelectionStates( m_pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? 0 : 1 );
+ }
+}
+
+void CFullscreenStorePreviewItem::ExitFullscreen()
+{
+ if ( !m_hOwner.Get() )
+ return;
+
+ if ( m_pPlayerModelPanel.Get() )
+ {
+ m_pPlayerModelPanel->SetParent( m_hOwner.Get() );
+ m_pPlayerModelPanel->SetBounds( m_OldModelState.m_aPlayerModelPanelBounds[0], m_OldModelState.m_aPlayerModelPanelBounds[1], m_OldModelState.m_aPlayerModelPanelBounds[2], m_OldModelState.m_aPlayerModelPanelBounds[3] );
+
+ // Reset the player position to it's pre-fullscreen location, but add on any zoom delta if needed.
+ const Vector vecZoomOffset = m_OldModelState.m_bZoomed != m_pPlayerModelPanel->IsZoomed() ? m_pPlayerModelPanel->GetZoomOffset() : vec3_origin;
+ m_pPlayerModelPanel->m_vecPlayerPos = m_OldModelState.m_vecPlayerPos + vecZoomOffset;
+ }
+
+ m_flGoFullscreenStartTime = 0.0f;
+ SetVisible( false );
+
+ PostMessage( m_hOwner.Get(), new KeyValues( "ExitFullscreen" ) );
+}
+
+bool CFullscreenStorePreviewItem::IsFullscreenMode()
+{
+ return IsVisible();
+}
+
+void CFullscreenStorePreviewItem::OnNavButtonSelected( KeyValues *pData )
+{
+ const int iTeam = pData->GetInt( "userdata", -1 ); AssertMsg( iTeam >= 0, "Bad filter" );
+ if ( iTeam < 0 )
+ return;
+
+ if ( !m_pPlayerModelPanel.Get() )
+ return;
+
+ m_pPlayerModelPanel->SetTeam( iTeam );
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "team_switch_%s(store_preview_item_panel_fullscreen)", iTeam == TF_TEAM_RED ? "red" : "blu" );
+}
+
+void CFullscreenStorePreviewItem::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( m_flGoFullscreenStartTime == 0.0f )
+ {
+ SetVisible( false );
+ return;
+ }
+
+ // We are fading, or already faded in
+ SetVisible( true );
+
+ // Keep track of mouse movement - if the mouse button is down, force the mouse-moving state so we
+ // don't end up fading out while the player is rotating or clicking-and-holding anywhere else
+ int nMouseX, nMouseY;
+ vgui::input()->GetCursorPos( nMouseX, nMouseY );
+ bool bMouseMoved = false;
+ const bool bMouseButtonDown = vgui::input()->IsMouseDown( MOUSE_LEFT );
+ const bool bForceMouseMoving = bMouseButtonDown;
+ if ( bForceMouseMoving || nMouseX != m_nLastMouseX || nMouseY != m_nLastMouseY )
+ {
+ bMouseMoved = true;
+ m_nLastMouseX = nMouseX;
+ m_nLastMouseY = nMouseY;
+ m_flLastMouseMoveTime = gpGlobals->realtime;
+ }
+
+ // Fade in the button blocker if the mouse has been idle for some period of time
+ if ( m_pOverlayPanel )
+ {
+ const float flButtonBlockerFade = SCurve(
+ LerpScale(
+ gpGlobals->realtime,
+ m_flLastMouseMoveTime + m_flUiFadeoutTime,
+ m_flLastMouseMoveTime + m_flUiFadeoutTime + m_flUiFadeoutDuration,
+ 0.0f,
+ 1.0f
+ )
+ );
+
+ m_pOverlayPanel->SetVisible( flButtonBlockerFade > 0.0f );
+
+ m_pOverlayPanel->SetAlpha( (int)( 255 * flButtonBlockerFade ) );
+
+ // Set to layer above overlay panel, so it will always be visible
+ m_pPlayerModelPanel->SetZPos( flButtonBlockerFade > 0.0f ? ( m_pOverlayPanel->GetZPos() + 1 ) : 0 );
+ }
+
+ const float flFade = SCurve(
+ LerpScale(
+ gpGlobals->realtime,
+ m_flGoFullscreenStartTime,
+ m_flGoFullscreenStartTime + m_flFullscreenFadeToBlackDuration,
+ 0.0f,
+ 1.0f
+ )
+ );
+
+ SetAlpha( (int)( 255 * flFade ) );
+
+ if ( !m_pPlayerModelPanel.Get() )
+ return;
+
+ // Resize 3D model panel
+ const int aDstBounds[4] = { 0, 0, ScreenWidth(), ScreenHeight() };
+ const int aBounds[4] = {
+ Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[0], aDstBounds[0] ),
+ Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[1], aDstBounds[1] ),
+ Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[2], aDstBounds[2] ),
+ Lerp( flFade, m_OldModelState.m_aPlayerModelPanelBounds[3], aDstBounds[3] )
+ };
+ m_pPlayerModelPanel->SetBounds( aBounds[0], aBounds[1], aBounds[2], aBounds[3] );
+
+ if ( flFade < 0.999f )
+ {
+ const Vector vecZoomOffset = m_pPlayerModelPanel->IsZoomed() ? m_pPlayerModelPanel->GetZoomOffset() : vec3_origin;
+ const Vector vecFullscreenOrigin( m_flModelPanelOriginX, m_flModelPanelOriginY, m_flModelPanelOriginZ );
+ m_pPlayerModelPanel->m_vecPlayerPos = Lerp( flFade, m_OldModelState.m_vecPlayerPos, vecFullscreenOrigin + vecZoomOffset );
+ }
+
+ if ( m_pZoomButton )
+ {
+ m_pZoomButton->SetEnabled( flFade == 1.0f );
+ }
+
+ if ( bMouseButtonDown && m_pRotLeftButton && m_pRotRightButton )
+ {
+ float flDeltaAngle = 0;
+ const float kScale = 100.0f;
+ if ( m_pRotLeftButton->IsWithin( nMouseX, nMouseY ) )
+ {
+ flDeltaAngle = -gpGlobals->frametime * kScale;
+ }
+ else if ( m_pRotRightButton->IsWithin( nMouseX, nMouseY ) )
+ {
+ flDeltaAngle = gpGlobals->frametime * kScale;
+ }
+
+ m_pPlayerModelPanel->RotateYaw( flDeltaAngle );
+
+ // Accumulate time rotation buttons are being pressed for stat tracking
+ m_Stats.m_flRotationTime += gpGlobals->frametime;
+ }
+}
+
+void CFullscreenStorePreviewItem::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/econ/store/v2/StorePreviewItemPanel_Fullscreen.res" );
+
+ m_pOverlayPanel = dynamic_cast<EditablePanel *>( FindChildByName( "OverlayPanel" ) );
+ m_pRotLeftButton = dynamic_cast<CExButton *>( FindChildByName( "RotateLeftButton" ) );
+ m_pRotRightButton = dynamic_cast<CExButton *>( FindChildByName( "RotateRightButton" ) );
+ m_pZoomButton = dynamic_cast<CExButton *>( FindChildByName( "ZoomButton" ) );
+ m_pTeamNavPanel = dynamic_cast<CNavigationPanel *>( FindChildByName( "TeamNavPanel" ) );
+}
+
+void CFullscreenStorePreviewItem::OnCommand( const char *command )
+{
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "on_command(store_preview_item_panel_fullscreen)", command );
+
+ if ( !V_strnicmp( command, "close", 6 ) )
+ {
+ ExitFullscreen();
+ return;
+ }
+
+ if ( m_hOwner.Get() )
+ {
+ // Let the owner handle the command
+ m_hOwner->OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePreviewItemPanel2::CTFStorePreviewItemPanel2( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner )
+: BaseClass( pParent, pResFile, "storepreviewitem", pOwner )
+{
+ m_pScrollBar = new ScrollBar( this, "ScrollBar", true );
+ m_pScrollBar->AddActionSignalTarget( this );
+
+ m_pFullscreenPanel = new CFullscreenStorePreviewItem( this, this );
+ m_pFullscreenPanel->AddActionSignalTarget( this );
+
+ m_pMouseOverItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mouseoveritempanel" ) );
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+ m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE );
+ m_pMouseOverItemPanel->MoveToFront();
+
+ m_pItemViewData = NULL;
+ m_pSOEconItemData = NULL;
+ Clear();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::Clear()
+{
+ m_pPlayerModelPanel = NULL;
+
+ m_pPreviewButton = NULL;
+ m_pDialogFrame = NULL;
+ m_pPreviewViewportBg = NULL;
+ m_pItemNameLabel = NULL;
+ m_pAttributesLabel = NULL;
+ m_pItemCollectionHighlight = NULL;
+ m_pDetailsView = NULL;
+ m_pDetailsViewChild = NULL; // Scrollable
+ m_pItemWikiPageButton = NULL;
+ m_pTeamNavPanel = NULL;
+ m_pCycleTextLabel = NULL;
+ m_pScrollableChild = NULL;
+ m_pGoFullscreenButton = NULL;
+ m_nNumAttribLinesAdded = 0;
+ m_bArmoryTextAdded = false;
+ m_nNumAttribLinesAdded = 0;
+ m_iSliderPos = 0;
+ m_aClickPos[0] = m_aClickPos[1] = 0;
+ m_bCloseOnUp = false;
+ m_bMouseWasDown = false;
+ m_bIsHalloweenOrFullmoonOnlyItem = false;
+
+ for ( int i = 0; i < ARRAYSIZE( m_pAddRentalToCartButtons ); i++ )
+ {
+ m_pAddRentalToCartButtons[i] = NULL;
+ }
+
+ m_vecReferenceItemPanels.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ Clear();
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ // Strip tooltips.
+ // We have all the same data already
+ m_pItemIcons[i]->GetItemPanel()->SetTooltip( NULL, NULL );
+ }
+
+ m_pDialogFrame = dynamic_cast<EditablePanel *>( FindChildByName( "DialogFrame" ) );
+ m_pPreviewButton = dynamic_cast<CExButton *>( FindChildByName( "TryItOutButton" ) );
+ m_pCycleTextLabel = dynamic_cast<CExLabel *>( FindChildByName( "CycleTextLabel" ) );
+ m_pGoFullscreenButton = dynamic_cast<CExImageButton *>( FindChildByName( "GoFullscreenButton" ) );
+
+ COMPILE_TIME_ASSERT( ARRAYSIZE( m_pAddRentalToCartButtons ) == 3 );
+#ifdef ENABLE_STORE_RENTAL_BACKEND
+ m_pAddRentalToCartButtons[0] = dynamic_cast<CExButton *>( FindChildByName( "AddRentalToCartButton_1Day" ) );
+ m_pAddRentalToCartButtons[1] = dynamic_cast<CExButton *>( FindChildByName( "AddRentalToCartButton_3Day" ) );
+ m_pAddRentalToCartButtons[2] = dynamic_cast<CExButton *>( FindChildByName( "AddRentalToCartButton_7Day" ) );
+#endif
+
+ if ( m_pDialogFrame )
+ {
+ m_pPreviewViewportBg = dynamic_cast<EditablePanel *>( m_pDialogFrame->FindChildByName( "PreviewViewportBg" ) );
+ m_pItemNameLabel = dynamic_cast<CExLabel *>( m_pDialogFrame->FindChildByName( "ItemNameLabel" ) );
+ m_pDetailsView = dynamic_cast<EditablePanel *>( m_pDialogFrame->FindChildByName( "DetailsView" ) );
+
+ if ( m_pDetailsView )
+ {
+ m_pDetailsViewChild = dynamic_cast<EditablePanel *>( m_pDetailsView->FindChildByName( "ScrollableChild" ) );
+
+ if ( !m_pDetailsViewChild )
+ {
+ m_pDetailsViewChild = m_pDetailsView;
+ }
+
+ m_pAttributesLabel = dynamic_cast<CExLabel *>( m_pDetailsViewChild->FindChildByName( "AttributesLabel" ) );
+ m_pItemCollectionHighlight = dynamic_cast<EditablePanel *>( m_pDetailsViewChild->FindChildByName( "collectionhighlight" ) );
+ if ( m_pItemCollectionHighlight )
+ {
+ m_pItemCollectionHighlight->InvalidateLayout( true, true );
+ }
+ m_pItemWikiPageButton = dynamic_cast<CExButton *>( m_pDetailsViewChild->FindChildByName( "ItemWikiPageButton" ) );
+
+ if ( m_pItemWikiPageButton )
+ {
+ m_pItemWikiPageButton->AddActionSignalTarget( this );
+ }
+ }
+ }
+
+ m_pTeamNavPanel = dynamic_cast<CNavigationPanel *>( FindChildByName( "TeamNavPanel" ) );
+
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+
+ SetState( PS_ITEM );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given two controls A and B, place B such that it is (nXOffset,nYOffset)
+// pixels offset from A, using it's content width or height, depending on bVertical.
+// pControlNameA can be NULL, in which case zeros will be used as the offset.
+//-----------------------------------------------------------------------------
+int CTFStorePreviewItemPanel2::PlaceControl( Panel *pParent, const char *pControlNameA, const char *pControlNameB, int nOffset, bool bVertical, bool bSizeAToContents/*=true*/, bool bUseContentSize/*=true*/ )
+{
+ if ( !pParent || !pControlNameB )
+ {
+ AssertMsg( 0, "Bad!" );
+ return 0;
+ }
+
+ Label *pControlA = pControlNameA ? dynamic_cast<Label *>( pParent->FindChildByName( pControlNameA ) ) : NULL;
+ Label *pControlB = dynamic_cast<Label *>( pParent->FindChildByName( pControlNameB ) );
+ if ( !pControlB )
+ {
+ return 0;
+ }
+
+ if ( !pControlA && bVertical )
+ {
+ pControlA = m_pLastNewLineControl;
+ }
+
+ int aSize[2] = { 0, 0 };
+ int aPos[2] = { 0, 0 };
+ if ( pControlA )
+ {
+ pControlA->SetVisible( true );
+
+ if ( bSizeAToContents )
+ {
+ pControlA->SizeToContents();
+ pControlA->InvalidateLayout( true );
+ }
+
+ if ( bUseContentSize )
+ {
+ pControlA->GetContentSize( aSize[0], aSize[1] );
+ }
+ else
+ {
+ pControlA->GetSize( aSize[0], aSize[1] );
+ }
+
+ pControlA->GetPos( aPos[0], aPos[1] );
+ }
+
+ int aOffset[2] = { 0, 0 };
+ if ( bVertical )
+ {
+ aOffset[1] = aSize[1] + nOffset;
+ }
+ else
+ {
+ aOffset[0] = aSize[0] + nOffset;
+ }
+
+ // NOTE: We add in the slider position here
+ pControlB->SetPos( aPos[0] + aOffset[0], aPos[1] + aOffset[1] );
+
+ pControlB->SetVisible( true );
+
+#if _DEBUG
+ /*
+ Msg( "control A: %s size: w=%i h=%i pos: (%i, %i)\n", pControlNameA, aSize[0], aSize[1], aPos[0], aPos[1] );
+ int x,y;
+ pControlB->GetPos(x,y);
+ Msg( "control B: %s pos: (%i, %i)\n", pControlNameB, x,y );
+ */
+#endif
+
+ if ( bVertical )
+ {
+ m_pLastNewLineControl = pControlB;
+ }
+
+ m_nViewMaxHeight = MAX( m_nViewMaxHeight, aPos[1] + aOffset[1] + pControlB->GetTall() );
+ return m_nViewMaxHeight;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::PerformLayout( void )
+{
+// BaseClass::PerformLayout(); // We override completely here
+
+ // center the icons (we need to redo some of the work of CStorePreviewItemPanel, because we
+ // center the base item icons along with our TF specific class ones)
+ int iNumItemIcons = 0;
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ if ( m_pItemIcons[i]->IsVisible() )
+ {
+ ++iNumItemIcons;
+ }
+ }
+
+ int iNumClassIcons = 0;
+ FOR_EACH_VEC( m_pClassIcons, i )
+ {
+ if ( m_pClassIcons[i]->IsVisible() )
+ {
+ ++iNumClassIcons;
+ }
+ }
+
+ if ( m_pDialogFrame && ( iNumItemIcons || iNumClassIcons ) )
+ {
+ int aDialogFramePos[2];
+ m_pDialogFrame->GetPos( aDialogFramePos[0], aDialogFramePos[1] );
+
+ int iCenterX = aDialogFramePos[0] + m_pDialogFrame->GetWide() / 4;
+ int interval = XRES(2);
+ int totalWidth = (iNumItemIcons > 0 ? iNumItemIcons * m_pItemIcons[0]->GetWide() : 0) + (iNumClassIcons * m_pClassIcons[0]->GetWide()) + (interval * (iNumItemIcons + iNumClassIcons - 1));
+ int iX = iCenterX - ( totalWidth / 2 );
+
+ int posX, posY;
+ if ( iNumItemIcons > 0 )
+ {
+ m_pItemIcons[0]->GetPos( posX, posY );
+ }
+ else
+ {
+ m_pClassIcons[0]->GetPos( posX, posY );
+ }
+
+ int iButton = 0;
+ for ( int i = 0; i < m_pItemIcons.Count(); i++ )
+ {
+ if ( m_pItemIcons[i]->IsVisible() )
+ {
+ m_pItemIcons[i]->SetPos( iX, posY );
+ iX += m_pItemIcons[i]->GetWide() + interval;
+
+ iButton++;
+ }
+ }
+
+ for ( int i = 0; i < m_pClassIcons.Count(); i++ )
+ {
+ if ( m_pClassIcons[i]->IsVisible() )
+ {
+ m_pClassIcons[i]->SetPos( iX, posY );
+ iX += m_pClassIcons[i]->GetWide() + interval;
+
+ iButton++;
+ }
+ }
+ }
+
+ if ( !m_pPreviewViewportBg || !m_pItemNameLabel || !m_pDetailsViewChild || !m_pDetailsView || !m_pDialogFrame )
+ {
+ m_pScrollBar->SetVisible( false );
+ return;
+ }
+
+ // Make sure we size the item name in case it needs multiple lines.
+ m_pItemNameLabel->SizeToContents();
+ m_pItemNameLabel->InvalidateLayout( true );
+ if ( m_pItemNameLabel && m_item.IsValid() )
+ {
+ const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( m_item.GetItemDefinition()->GetRarity() );
+
+ // Setup the rarity color overlay
+ {
+ Color color( 255, 255, 255, 255 );
+ if ( pItemRarity )
+ {
+ attrib_colors_t attribColor = pItemRarity->GetAttribColor();
+ vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ color = pScheme->GetColor( GetColorNameForAttribColor( attribColor ), Color( 255, 255, 255, 255 ) );
+ }
+ m_pItemNameLabel->SetColorStr( color );
+ }
+ }
+
+ int aItemNameLabelPos[2];
+ m_pItemNameLabel->GetPos( aItemNameLabelPos[0], aItemNameLabelPos[1] );
+
+ // This is the item label's new bottom y coordinate after it's been sized
+ const int nNewYRelativeToDlgFrame = aItemNameLabelPos[1] + m_pItemNameLabel->GetTall();
+
+ // Set ypos for details view and scroll bar
+ int aDetailsViewPos[2];
+ m_pDetailsView->GetPos( aDetailsViewPos[0], aDetailsViewPos[1] );
+ m_pDetailsView->SetPos( aDetailsViewPos[0], nNewYRelativeToDlgFrame );
+
+ int aDialogFramePos[2];
+ m_pDialogFrame->GetPos( aDialogFramePos[0], aDialogFramePos[1] );
+
+ if ( m_pScrollBar )
+ {
+ int aScrollBar[2];
+ m_pScrollBar->GetPos( aScrollBar[0], aScrollBar[1] );
+ m_pScrollBar->SetPos( aScrollBar[0], nNewYRelativeToDlgFrame + aDialogFramePos[1] );
+ }
+
+ int nNewHeight = m_pPreviewViewportBg->GetTall() - m_pItemNameLabel->GetTall();
+ m_pDetailsView->SetTall( nNewHeight );
+ if ( m_pScrollBar )
+ {
+ m_pScrollBar->SetTall( nNewHeight );
+ }
+
+ // Place paint and style buttons
+ CUtlVector<CExButton *> vecVisibleButtons;
+ const int nNumPossibilyVisibleWeapons = 1;
+ CExButton *pPossiblyVisibleButtons[nNumPossibilyVisibleWeapons] = { m_pNextWeaponButton };
+ for ( int i = 0; i < nNumPossibilyVisibleWeapons; ++i )
+ {
+ CExButton *pCurButton = pPossiblyVisibleButtons[i];
+ if ( !pCurButton || !pCurButton->IsVisible() )
+ continue;
+
+ vecVisibleButtons.AddToTail( pCurButton );
+ }
+
+ int nNumButtonsNeeded = vecVisibleButtons.Count();
+ if ( nNumButtonsNeeded )
+ {
+ // Center however many buttons we need to along the top of the viewport
+ int aViewportPos[2];
+ m_pPreviewViewportBg->GetPos( aViewportPos[0], aViewportPos[1] );
+ for ( int i = 0; i < nNumButtonsNeeded; ++i )
+ {
+ CExButton *pCurButton = vecVisibleButtons[i];
+ pCurButton->SetPos( aDialogFramePos[0] + aViewportPos[0] + ( i + 1 ) * m_pPreviewViewportBg->GetWide() / ( nNumButtonsNeeded + 1 ) - m_iControlButtonWidth / 2, m_iControlButtonY );
+ pCurButton->SetSize( m_iControlButtonWidth, m_iControlButtonHeight );
+ }
+ }
+
+ m_pLastNewLineControl = NULL;
+ m_nViewMaxHeight = 0;
+
+ PlaceControl( m_pDetailsViewChild, NULL, "ItemLevelInfoLabel", m_iSmallVerticalBreakSize, true );
+
+ if ( m_bIsHalloweenOrFullmoonOnlyItem )
+ {
+ PlaceControl( m_pDetailsViewChild, "ItemLevelInfoLabel", "RestrictionsLabel", m_iMediumVerticalBreakSize, true );
+ PlaceControl( m_pDetailsViewChild, "RestrictionsLabel", "RestrictionsTextLabel", m_iHorizontalBreakSize, false );
+ PlaceControl( m_pDetailsViewChild, "RestrictionsLabel", "UsedByLabel", m_iSmallVerticalBreakSize, true );
+ }
+ else
+ {
+ PlaceControl( m_pDetailsViewChild, "ItemLevelInfoLabel", "UsedByLabel", m_iMediumVerticalBreakSize, true );
+ }
+
+ PlaceControl( m_pDetailsViewChild, "UsedByLabel", "UsedByTextLabel", m_iHorizontalBreakSize, false );
+ PlaceControl( m_pDetailsViewChild, "UsedByLabel", "SlotLabel", m_iSmallVerticalBreakSize, true );
+ PlaceControl( m_pDetailsViewChild, "SlotLabel", "SlotTextLabel", m_iHorizontalBreakSize, false );
+ PlaceControl( m_pDetailsViewChild, "SlotLabel", "PriceLabel", m_iBigVerticalBreakSize, true );
+
+ if ( m_bArmoryTextAdded )
+ {
+ PlaceControl( m_pDetailsViewChild, "PriceLabel", "ArmoryTextLabel", m_iBigVerticalBreakSize, true );
+ PlaceControl( m_pDetailsViewChild, "ArmoryTextLabel", "AttributesLabel", m_iBigVerticalBreakSize, true );
+ }
+ else
+ {
+ PlaceControl( m_pDetailsViewChild, "PriceLabel", "AttributesLabel", m_iBigVerticalBreakSize, true );
+ }
+
+ PlaceControl( m_pDetailsViewChild, m_nNumAttribLinesAdded == 0 ? "PriceLabel" : "AttributesLabel", "ItemWikiPageButton", m_iBigVerticalBreakSize, true );
+
+ PlaceControl( m_pDetailsViewChild, "ItemWikiPageButton", "TradableLabel", m_iBigVerticalBreakSize, true, false, false );
+ PlaceControl( m_pDetailsViewChild, "TradableLabel", "TradableTextLabel", m_iHorizontalBreakSize, false );
+ PlaceControl( m_pDetailsViewChild, "TradableLabel", "CraftableLabel", m_iSmallVerticalBreakSize, true );
+ PlaceControl( m_pDetailsViewChild, "CraftableLabel", "CraftableTextLabel", m_iHorizontalBreakSize, false );
+ PlaceControl( m_pDetailsViewChild, "CraftableLabel", "GiftableLabel", m_iSmallVerticalBreakSize, true );
+ PlaceControl( m_pDetailsViewChild, "GiftableLabel", "GiftableTextLabel", m_iHorizontalBreakSize, false );
+ PlaceControl( m_pDetailsViewChild, "GiftableLabel", "NameableLabel", m_iSmallVerticalBreakSize, true );
+ PlaceControl( m_pDetailsViewChild, "NameableLabel", "NameableTextLabel", m_iHorizontalBreakSize, false );
+
+ if ( m_pScrollBar )
+ {
+ m_pScrollBar->SetVisible( true );
+ m_pScrollBar->InvalidateLayout( true );
+ m_pScrollBar->SetRange( 0, m_nViewMaxHeight );
+ m_pScrollBar->SetRangeWindow( m_pScrollBar->GetTall() );
+
+ m_pDetailsViewChild->SetTall( m_nViewMaxHeight );
+ UpdateScrollableChild();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::UpdateScrollableChild()
+{
+ m_pDetailsViewChild->SetPos( 0, -m_iSliderPos );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnCommand( const char *command )
+{
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "on_command(store_preview_item_panel)", command );
+
+ if ( !V_strnicmp( command, "closex", 5 ) )
+ {
+ // This is just a way for us to differentiate between 'x' button being pressed vs.
+ // the "back" button vs. clicking outside the preview window.
+ DoClose();
+ }
+ else if ( !V_strnicmp( command, "tryitout", 8 ) )
+ {
+ // OGS data gets written elsewhere for this event
+ PostMessage( m_pOwner, new KeyValues( "PreviewItem", "item_def_index", m_item.GetItemDefIndex() ) );
+ DoClose();
+ }
+ else if ( !V_strnicmp( command, "addtocart", 9 )
+#ifdef ENABLE_STORE_RENTAL_BACKEND
+ || !V_strnicmp( command, "addrentaltocart", 15 )
+#endif
+ )
+ {
+#ifdef ENABLE_STORE_RENTAL_BACKEND
+ ECartItemType eCartItemType = !V_stricmp( command, "addrentaltocart_1day" )
+ ? kCartItem_Rental_1Day
+ : !V_stricmp( command, "addrentaltocart_3day" )
+ ? kCartItem_Rental_3Day
+ : !V_stricmp( command, "addrentaltocart_7day" )
+ ? kCartItem_Rental_7Day
+ : kCartItem_Purchase;
+#else
+ ECartItemType eCartItemType = kCartItem_Purchase;
+#endif
+
+ KeyValues *pParams = new KeyValues( "AddItemToCart" );
+ pParams->SetInt( "item_def", m_item.GetItemDefIndex() );
+ pParams->SetInt( "cart_add_type", eCartItemType );
+ PostMessage( m_pOwner, pParams );
+ DoClose();
+ }
+ else if ( !V_strnicmp( command, "viewwikipage", 12 ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() && m_pItemFullImage )
+ {
+ CEconItemView *pItem = m_pItemFullImage->GetItem();
+ if ( pItem->IsValid() )
+ {
+ // Determine which language we should use
+ char uilanguage[ 64 ];
+ uilanguage[0] = 0;
+ engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
+ ELanguage iLang = PchLanguageToELanguage( uilanguage );
+
+ char szURL[512];
+ Q_snprintf( szURL, sizeof(szURL), "http://wiki.teamfortress.com/scripts/itemredirect.php?id=%d&lang=%s", pItem->GetItemDefIndex(), GetLanguageICUName( iLang ) );
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL );
+
+ C_CTF_GameStats.Event_Catalog( IE_ARMORY_BROWSE_WIKI, NULL, pItem );
+ }
+ }
+ }
+ else if ( !V_strnicmp( command, "team_", 5 ) )
+ {
+ const char *pTeam = command + 5;
+ if ( !V_strnicmp( pTeam, "red", 3 ) )
+ {
+ m_pPlayerModelPanel->SetTeam( TF_TEAM_RED );
+ }
+ else
+ {
+ m_pPlayerModelPanel->SetTeam( TF_TEAM_BLUE );
+ }
+ }
+ else if ( !V_strnicmp( command, "gofullscreen", 11 ) )
+ {
+ if ( m_pFullscreenPanel )
+ {
+ m_pFullscreenPanel->GoFullscreen( m_pPlayerModelPanel );
+ }
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnClassIconSelected( KeyValues *data )
+{
+ BaseClass::OnClassIconSelected( data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnHideClassIconMouseover( void )
+{
+ BaseClass::OnHideClassIconMouseover();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnShowClassIconMouseover( KeyValues *data )
+{
+ // We decided not to show the "this item is
+ // usable by the [Class name]" tooltip.
+ //BaseClass::OnShowClassIconMouseover( data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::PreviewItemCopy( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry )
+{
+ // Make a copy of SO data since it comes from the market and will fall out of scope
+ if ( m_pItemViewData )
+ {
+ delete m_pItemViewData;
+ m_pItemViewData = NULL;
+ }
+
+ if ( m_pSOEconItemData )
+ {
+ delete m_pSOEconItemData;
+ m_pSOEconItemData = NULL;
+ }
+
+ m_pItemViewData = new CEconItemView( *pItem );
+ m_pSOEconItemData = new CEconItem( *pItem->GetSOCData() );
+ m_pItemViewData->SetNonSOEconItem( m_pSOEconItemData );
+ PreviewItem( iClass, m_pItemViewData, pEntry );
+}
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry )
+{
+ BaseClass::PreviewItem( iClass, pItem, pEntry );
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_preview_item_panel(preview_item)", CFmtStr( "%i", m_item.GetItemDefIndex() ).Access() );
+
+ // Reload the .res file right now, since we need to make sure the item name label, on which all other controls
+ // base their position, is valid.
+ InvalidateLayout( true, true );
+
+ // Update the fullscreen item def index
+ if ( m_pFullscreenPanel )
+ {
+ m_pFullscreenPanel->SetItemDef( m_item.GetItemDefIndex() );
+ }
+
+ // If we didn't have a store entry passed in, look for one.
+ if ( !pEntry )
+ {
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( pStorePanel )
+ {
+ pEntry = pStorePanel->GetPriceSheet()->GetEntry( m_item.GetItemDefIndex() );
+ }
+ }
+
+ if ( m_pDialogFrame && m_pDetailsView && m_pItemFullImage && m_pItemFullImage->GetItem() && m_pAttributesLabel )
+ {
+ const CEconItemView *pFullItem = m_pItemFullImage->GetItem();
+ const CEconItemDefinition *pBaseDef = pFullItem->GetItemDefinition();
+ const CTFItemDefinition *pDef = dynamic_cast<const CTFItemDefinition *>( pBaseDef );
+
+ if ( pFullItem && pDef )
+ {
+ m_pDialogFrame->SetDialogVariable( "itemname", pFullItem->GetItemName() );
+
+ // Holiday restrictions?
+ const char *pHolidayRestriction = pDef->GetHolidayRestriction() ? pDef->GetHolidayRestriction() : "";
+ m_bIsHalloweenOrFullmoonOnlyItem = StringHasPrefix( pHolidayRestriction, "halloween" );
+
+ CTFItemSchema *pSchema = ItemSystem()->GetItemSchema();
+ if ( pSchema )
+ {
+ // Build a list of classes by which this item can be used
+ const CBitVec<LOADOUT_COUNT> *pbvClassUsability = pDef->GetClassUsability();
+
+ const int kClassNamesSize = 512;
+ wchar_t wszClassNames[ kClassNamesSize ] = L"";
+ int nClassAdded = 0;
+ bool bAllClasses = true;
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i )
+ {
+ if ( !pbvClassUsability->IsBitSet( i ) )
+ {
+ bAllClasses = false;
+ break;
+ }
+ }
+
+ if ( bAllClasses )
+ {
+ m_pDetailsViewChild->SetDialogVariable( "used_by_classes", g_pVGuiLocalize->Find( "#Store_ItemDesc_AllClasses" ) );
+ }
+ else
+ {
+ for ( int i = 0; i < LOADOUT_COUNT; ++i )
+ {
+ if ( pbvClassUsability->IsBitSet( i ) )
+ {
+ V_wcscat_safe( wszClassNames, nClassAdded++ == 0 ? L"" : L", " ); // add empty lines everywhere except before the first line
+ V_wcscat_safe( wszClassNames, g_pVGuiLocalize->Find( g_aPlayerClassNames[i] ) );
+ }
+ }
+ m_pDetailsViewChild->SetDialogVariable( "used_by_classes", wszClassNames );
+ }
+
+ // Setup the slot string
+ const CUtlVector< const char * > &vecLoadoutStrings = pSchema->GetLoadoutStrings( pDef->GetEquipType() );
+ const int iSlot = pDef->GetDefaultLoadoutSlot();
+ const bool bSlotValid = vecLoadoutStrings.IsValidIndex( iSlot );
+ m_pDetailsViewChild->SetDialogVariable( "slot", g_pVGuiLocalize->Find( bSlotValid ? CFmtStr( "#LoadoutSlot_%s", vecLoadoutStrings[iSlot] ).Access() : "#Store_ItemDesc_Slot_None" ) );
+
+ // Make an attempt to display tradability accurately even though we don't have an item to pull from.
+ static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" );
+ Assert( pAttrib_CannotTrade );
+
+ // Get localized versions of "yes" and "no"
+ const wchar_t *pYesNo[2] = {
+ g_pVGuiLocalize->Find( "#Store_ItemDesc_Yes" ),
+ g_pVGuiLocalize->Find( "#Store_ItemDesc_No" )
+ };
+ bool bIsMapStamp = pDef->GetItemClass() && !V_strncmp( pDef->GetItemClass(), "map_token", 9 );
+ bool bIsTradeable = bIsMapStamp || FindAttribute( pDef, pAttrib_CannotTrade )
+ ? pYesNo[ 1 ]
+ : g_pVGuiLocalize->Find( "#Attrib_Store_TradableAfterDate" );
+
+ m_pDetailsViewChild->SetDialogVariable( "giftable", bIsTradeable );
+ m_pDetailsViewChild->SetDialogVariable( "nameable", ( pDef->GetCapabilities() & ITEM_CAP_NAMEABLE ) != 0 ? pYesNo[0] : pYesNo[1] );
+
+ m_pDetailsViewChild->SetDialogVariable( "tradable", bIsTradeable );
+
+ // No store-bought items are craftable, but items that were going to be tradable would show as craftable because
+ // they weren't real items with a real origin that would prevent them from being crafted. In the short term it makes
+ // more sense to just force this to always display "false" because at least it will never be wrong.
+ m_pDetailsViewChild->SetDialogVariable( "craftable", pDef->IsBundle()
+ ? g_pVGuiLocalize->Find( "#Attrib_CannotCraftWeapons" )
+ : pDef->GetCapabilities() & ITEM_CAP_CAN_BE_CRAFTED_IF_PURCHASED
+ ? pYesNo[0]
+ : pYesNo[1] );
+
+ // Setup price
+ if ( pEntry )
+ {
+ ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ int iTotalPrice = pEntry->GetCurrentPrice( eCurrency );
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iTotalPrice, eCurrency );
+
+#ifdef ENABLE_STORE_RENTAL_BACKEND
+ const wchar_t *pwsRentalPriceFormat = GLocalizationProvider()->Find( "#TF_Store_RentalPriceFormat" );
+ if ( pEntry->IsRentable() && pwsRentalPriceFormat )
+ {
+ wchar_t wzRentalLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzRentalLocalizedPrice, ARRAYSIZE( wzRentalLocalizedPrice ), pEntry->GetRentalPriceScale() * iTotalPrice, eCurrency )
+
+ wchar_t wzLocalizedPriceString[96];
+ ::ILocalize::ConstructString_safe( wzLocalizedPriceString, pwsRentalPriceFormat, 2, wzLocalizedPrice, wzRentalLocalizedPrice );
+ m_pDetailsViewChild->SetDialogVariable( "price", wzLocalizedPriceString );
+ }
+ else
+#endif
+ {
+ if ( pEntry->m_bIsMarketItem )
+ {
+ if ( iTotalPrice != 0 )
+ {
+ wchar_t wzMarketString[96];
+ g_pVGuiLocalize->ConstructString_safe(
+ wzMarketString,
+ LOCCHAR( "%s1 %s2" ),
+ 2,
+ g_pVGuiLocalize->Find( "#Store_StartingAt" ),
+ wzLocalizedPrice );
+
+ m_pDetailsViewChild->SetDialogVariable( "price", wzMarketString );
+ }
+ else
+ {
+ m_pDetailsViewChild->SetDialogVariable( "price", "..." );
+ }
+ }
+ else
+ {
+ // if market item. Prefix 'Starting at'
+ m_pDetailsViewChild->SetDialogVariable( "price", wzLocalizedPrice );
+ }
+ }
+ }
+
+ // Show/hide rental button.
+ for ( int i = 0; i < ARRAYSIZE( m_pAddRentalToCartButtons ); i++ )
+ {
+ if ( m_pAddRentalToCartButtons[i] )
+ {
+ m_pAddRentalToCartButtons[i]->SetVisible( pEntry && pEntry->IsRentable() );
+ }
+ }
+ }
+
+ // Final label value for our armory description text block.
+ const wchar_t *pwszLabelValue = L"";
+ m_bArmoryTextAdded = false;
+
+ const char *pszArmoryDescString = pDef->GetArmoryDescString();
+ if ( pszArmoryDescString )
+ {
+ const ArmoryStringDict_t& ArmoryKeys = GetItemSchema()->GetArmoryDataItems();
+ const ArmoryStringDict_t::IndexType_t armoryIndex = ArmoryKeys.Find( pszArmoryDescString );
+
+ if ( ArmoryKeys.IsValidIndex( armoryIndex ) )
+ {
+ const char *pszArmoryDescLocalizationKey = ArmoryKeys[ armoryIndex ].Get();
+
+ pwszLabelValue = g_pVGuiLocalize->Find( pszArmoryDescLocalizationKey );
+ m_bArmoryTextAdded = true;
+ }
+ }
+
+ m_pDetailsViewChild->SetDialogVariable( "armory_text", pwszLabelValue );
+ }
+
+ // clear all old reference item panels before adding new ones
+ FOR_EACH_VEC( m_vecReferenceItemPanels, i )
+ {
+ m_vecReferenceItemPanels[i]->MarkForDeletion();
+ }
+ m_vecReferenceItemPanels.RemoveAll();
+ int iReferenceItemHeight = 0;
+
+ const CEconItemDescription *pDescription = m_pItemFullImage->GetItem()->GetDescription();
+ if ( pDescription )
+ {
+ TextImage *pAttributesTextImage = m_pAttributesLabel->GetTextImage(); // This pointer already verified above
+ pAttributesTextImage->ClearColorChangeStream();
+
+ const int kAttribBufferSize = 4 * 1024;
+ wchar_t wszAttribBuffer[ kAttribBufferSize ] = L"";
+
+ int iAttribLine = 0;
+ m_nNumAttribLinesAdded = 0;
+ Color clrPrev( 0, 0, 0, 0 );
+ uint32 unCurrentTextStreamIndex = 0;
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ int iFontHeight = surface()->GetFontTall( m_pAttributesLabel->GetFont() );
+
+ if ( m_pItemCollectionHighlight )
+ {
+ m_pItemCollectionHighlight->SetVisible( false );
+ }
+
+ iReferenceItemHeight = iFontHeight;
+
+ int iAttributePanelX, iAttributePanelY;
+ m_pAttributesLabel->GetPos( iAttributePanelX, iAttributePanelY );
+
+ for ( uint32 i = 0; i < pDescription->GetLineCount(); ++i )
+ {
+ const econ_item_description_line_t& line = pDescription->GetLine(i);
+ int nLineLength = StringFuncs<locchar_t>::Length( line.sText.Get() );
+ if ( ( line.unMetaType & kDescLineFlag_Type ) != 0 )
+ {
+ m_pDetailsViewChild->SetDialogVariable( "item_level_info", line.sText.Get() );
+ }
+ else if ( ( line.unMetaType & kDescLineFlagSet_DisplayInAttributeBlock ) != 0 && m_pAttributesLabel )
+ {
+ ++iAttribLine;
+
+ // current collection item line
+ bool bIsCurrentCollectionItem = ( line.unMetaType & kDescLineFlag_CollectionCurrentItem ) != 0;
+ // use bg color as text color for current item for a better highlight
+ Color col = bIsCurrentCollectionItem ? Color( 0, 0, 0, 255 ) : pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) );
+
+ // Output a color change if necessary.
+ if ( i == 0 || clrPrev != col )
+ {
+ pAttributesTextImage->AddColorChange( col, unCurrentTextStreamIndex );
+ clrPrev = col;
+ }
+
+ // Current line highlight
+ if ( bIsCurrentCollectionItem && m_pItemCollectionHighlight )
+ {
+ // use text color as bg color for the current item for a better highlight
+ Color bgColor = pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) );
+
+ // Get the current ypos
+ int x, y;
+ m_pAttributesLabel->GetPos( x, y );
+ m_pItemCollectionHighlight->SetPos( x, y + ( iAttribLine - 1 ) * iFontHeight );
+ m_pItemCollectionHighlight->SetBgColor( bgColor );
+ m_pItemCollectionHighlight->SetVisible( bIsCurrentCollectionItem );
+ }
+
+ if ( ( line.unMetaType & ( kDescLineFlag_Name | kDescLineFlag_Type ) ) == 0 )
+ {
+ V_wcscat_safe( wszAttribBuffer, m_nNumAttribLinesAdded++ == 0 ? L"" : L"\n" ); // add empty lines everywhere except before the first line
+ V_wcscat_safe( wszAttribBuffer, line.sText.Get() );
+ unCurrentTextStreamIndex += nLineLength + 1; // add one character to deal with newlines
+ }
+
+ if ( line.unDefIndex != INVALID_ITEM_DEF_INDEX )
+ {
+ // set text and recalculate the size now to compute for button pos
+ m_pAttributesLabel->SetText( wszAttribBuffer );
+ m_pAttributesLabel->SizeToContents();
+
+ CItemModelPanel* pItemModelPanel = new CItemModelPanel( m_pAttributesLabel, CFmtStr( "reference_item_%d", m_vecReferenceItemPanels.Count() ) );
+ pItemModelPanel->SetActAsButton( true, true );
+ pItemModelPanel->SetAutoDelete( true );
+
+ pItemModelPanel->SetPos( 0, m_pAttributesLabel->GetTall() - iFontHeight );
+ pItemModelPanel->SetZPos( m_pAttributesLabel->GetZPos() + 1 );
+
+ CEconItemView itemData;
+ itemData.Init( line.unDefIndex, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ itemData.SetClientItemFlags( kEconItemFlagClient_Preview );
+ pItemModelPanel->SetItem( &itemData );
+ pItemModelPanel->MakeFakeButton();
+
+ pItemModelPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ m_vecReferenceItemPanels.AddToTail( pItemModelPanel );
+ }
+ }
+ }
+
+ // Make sure our string is NUL-terminated.
+ wszAttribBuffer[ kAttribBufferSize-1 ] = 0;
+
+ m_pAttributesLabel->SetText( wszAttribBuffer );
+ }
+
+ // match the highlight width to attribute width
+ if ( m_pItemCollectionHighlight && m_pItemCollectionHighlight->IsVisible() )
+ {
+ m_pAttributesLabel->SizeToContents();
+ m_pItemCollectionHighlight->SetWide( m_pAttributesLabel->GetWide() );
+ }
+
+ if ( m_vecReferenceItemPanels.Count() )
+ {
+ m_pAttributesLabel->SizeToContents();
+ FOR_EACH_VEC( m_vecReferenceItemPanels, i )
+ {
+ m_vecReferenceItemPanels[i]->SetSize( m_pAttributesLabel->GetWide(), iReferenceItemHeight );
+ }
+ }
+
+ // Get PerformLayout() called, now that we have text in our controls - without this, SizeToContents() sizes all the labels as tall and narrow.
+ InvalidateLayout( true );
+ }
+
+ // Set the visibility of the "Try it now!" button based on whether we as a client think this item should be able
+ // to be previewed.
+ const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet();
+ if ( pPriceSheet && m_pPreviewButton )
+ {
+ const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( m_item.GetItemDefIndex() );
+ m_pPreviewButton->SetVisible( pStoreEntry && pStoreEntry->CanPreview() );
+ }
+
+ m_pItemFullImage->InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::SetState( preview_state_t iState )
+{
+ BaseClass::SetState( iState );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::UpdateIcons( void )
+{
+ BaseClass::UpdateIcons();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::UpdatePlayerModelButtons()
+{
+ BaseClass::UpdatePlayerModelButtons();
+
+ if ( m_pPlayerModelPanel )
+ {
+ if ( m_pTeamNavPanel )
+ {
+ m_pTeamNavPanel->SetVisible( m_pPlayerModelPanel->IsVisible() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::SetPlayerModelVisible( bool bVisible )
+{
+ BaseClass::SetPlayerModelVisible( bVisible );
+
+ if ( m_pCycleTextLabel )
+ {
+ m_pCycleTextLabel->SetVisible( bVisible );
+ }
+
+ if ( m_pGoFullscreenButton )
+ {
+ m_pGoFullscreenButton->SetVisible( bVisible );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText )
+{
+ BaseClass::SetCycleLabelText( pTargetLabel, pCycleText );
+
+ if ( m_pCycleTextLabel )
+ {
+ const wchar_t *pwszText = g_pVGuiLocalize->Find( pCycleText );
+ m_pCycleTextLabel->SetText( pwszText ? pwszText : L"" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnNavButtonSelected( KeyValues *pData )
+{
+ const int iTeam = pData->GetInt( "userdata", -1 ); AssertMsg( iTeam >= 0, "Bad filter" );
+ if ( iTeam < 0 )
+ return;
+
+ if ( !m_pPlayerModelPanel )
+ return;
+
+ m_pPlayerModelPanel->SetTeam( iTeam );
+ CyclePaint( false );
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "team_switch(store_preview_item_panel)", iTeam == TF_TEAM_RED ? "red" : "blu" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnExitFullscreen( KeyValues *pData )
+{
+ if ( !m_pPlayerModelPanel )
+ return;
+
+ // If team or class changed in fullscreen mode, update our ui components here
+ if ( m_pTeamNavPanel )
+ {
+ m_pTeamNavPanel->UpdateButtonSelectionStates( m_pPlayerModelPanel->GetTeam() == TF_TEAM_RED ? 0 : 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnTick( void )
+{
+ BaseClass::OnTick();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnMouseWheeled( int delta )
+{
+ if ( !m_pScrollBar )
+ return;
+
+ int val = m_pScrollBar->GetValue();
+ val -= (delta * 50);
+ m_pScrollBar->SetValue( val );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnSliderMoved( int position )
+{
+ m_iSliderPos = position;
+ UpdateScrollableChild();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::OnThink()
+{
+ BaseClass::OnThink();
+
+ Assert( IsVisible() );
+
+ // If the user clicks outside of the dialog frame, close the preview, like
+ // many web sites do on the internet.
+ bool bMouseDown = vgui::input()->IsMouseDown( MOUSE_LEFT );
+
+ // User just clicked?
+ if ( !m_pFullscreenPanel || !m_pFullscreenPanel->IsFullscreenMode() )
+ {
+ if ( !m_bMouseWasDown && bMouseDown )
+ {
+ vgui::input()->GetCursorPos( m_aClickPos[0], m_aClickPos[1] );
+ m_bMouseWasDown = true;
+ }
+ else if ( m_pDialogFrame && bMouseDown && !m_pDialogFrame->IsWithin( m_aClickPos[0], m_aClickPos[1] ) )
+ {
+ //m_bCloseOnUp = true;
+ }
+ else if ( !bMouseDown )
+ {
+ if ( m_bCloseOnUp )
+ {
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_preview_item_panel", "close_from_outside_click" );
+
+ DoClose();
+ }
+ m_bCloseOnUp = false;
+ m_bMouseWasDown = false;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStorePreviewItemPanel2::DoClose()
+{
+ if ( m_pFullscreenPanel )
+ {
+ m_pFullscreenPanel->ExitFullscreen();
+ }
+
+ OnClose();
+
+ SetVisible( false );
+}
diff --git a/game/client/tf/vgui/store/v2/tf_store_preview_item2.h b/game/client/tf/vgui/store/v2/tf_store_preview_item2.h
new file mode 100644
index 0000000..05f0657
--- /dev/null
+++ b/game/client/tf/vgui/store/v2/tf_store_preview_item2.h
@@ -0,0 +1,177 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_STORE_PREVIEW_ITEM2_H
+#define TF_STORE_PREVIEW_ITEM2_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/tf_store_preview_item_base.h"
+
+namespace vgui
+{
+ class ScrollBar;
+};
+class CNavigationPanel;
+class CExLabel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CFullscreenStorePreviewItem : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CFullscreenStorePreviewItem, EditablePanel );
+public:
+ CFullscreenStorePreviewItem( vgui::Panel *pParent, EditablePanel *pOwner );
+
+ void SetItemDef( itemid_t iItemDef );
+
+ void GoFullscreen( CTFPlayerModelPanel *pPlayerModelPanel );
+ void ExitFullscreen();
+ bool IsFullscreenMode();
+
+private:
+ MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData );
+
+ virtual void OnThink();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+
+ itemid_t m_iItemDef;
+
+ CExLabel *m_pCycleTextLabel;
+ CNavigationPanel *m_pTeamNavPanel;
+ CExButton *m_pPreviewButton;
+
+ struct ModelState_t
+ {
+ int m_aPlayerModelPanelBounds[4];
+ Vector m_vecPlayerPos;
+ bool m_bZoomed;
+ }
+ m_OldModelState;
+
+ struct Stats_t
+ {
+ Stats_t() { Clear(); }
+ void Clear() { V_memset( this, 0, sizeof( Stats_t ) ); }
+
+ float m_flRotationTime;
+ }
+ m_Stats;
+
+ float m_flGoFullscreenStartTime;
+ bool m_bIsHalloweenOrFullmoonOnlyItem;
+ vgui::DHANDLE< CTFPlayerModelPanel > m_pPlayerModelPanel;
+
+ CExButton *m_pZoomButton;
+ CExButton *m_pRotLeftButton;
+ CExButton *m_pRotRightButton;
+
+ EditablePanel *m_pOverlayPanel;
+
+ PHandle m_hOwner;
+
+ int m_nLastMouseX;
+ int m_nLastMouseY;
+ float m_flLastMouseMoveTime;
+
+ CPanelAnimationVar( float, m_flFullscreenFadeToBlackDuration, "fullscreen_fade_to_black_duration", "1.0" );
+ CPanelAnimationVar( float, m_flModelPanelOriginX, "fullscreen_modelpanel_origin_x", "170" );
+ CPanelAnimationVar( float, m_flModelPanelOriginY, "fullscreen_modelpanel_origin_y", "0" );
+ CPanelAnimationVar( float, m_flModelPanelOriginZ, "fullscreen_modelpanel_origin_z", "-36" );
+ CPanelAnimationVar( float, m_flUiFadeoutTime, "ui_fadeout_time", "5.0" );
+ CPanelAnimationVar( float, m_flUiFadeoutDuration, "ui_fadeout_duration", "1.0" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePreviewItemPanel2 : public CTFStorePreviewItemPanelBase
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePreviewItemPanel2, CTFStorePreviewItemPanelBase );
+public:
+ CTFStorePreviewItemPanel2( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner );
+
+ virtual void PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL ) OVERRIDE;
+ void PreviewItemCopy( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL );
+ virtual void SetState( preview_state_t iState );
+
+ MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data );
+ MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" );
+ MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data );
+
+protected:
+ virtual void OnThink();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void OnTick( void );
+ virtual void OnMouseWheeled( int delta );
+
+ int PlaceControl( Panel *pParent, const char *pControlNameA, const char *pControlNameB, int nOffset, bool bVertical,
+ bool bSizeAToContents = true, bool bUseContentSize = true );
+ void DoClose();
+ void Clear();
+ void UpdateScrollableChild();
+
+ virtual void SetPlayerModelVisible( bool bVisible );
+ virtual void UpdateIcons( void );
+ virtual void UpdatePlayerModelButtons( void );
+ virtual void SetCycleLabelText( vgui::Label *pTargetLabel, const char *pCycleText );
+
+ MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData );
+ MESSAGE_FUNC_PARAMS( OnExitFullscreen, "ExitFullscreen", pData );
+
+ Label *m_pLastNewLineControl;
+ EditablePanel *m_pDialogFrame; /// The background border
+ EditablePanel *m_pPreviewViewportBg;
+ CExLabel *m_pItemNameLabel;
+ CExLabel *m_pAttributesLabel;
+ vgui::EditablePanel *m_pItemCollectionHighlight;
+ CExLabel *m_pCycleTextLabel;
+ int m_nNumAttribLinesAdded;
+ bool m_bArmoryTextAdded;
+ EditablePanel *m_pDetailsView;
+ EditablePanel *m_pDetailsViewChild;
+ CExButton *m_pAddRentalToCartButtons[3];
+ EditablePanel *m_pScrollableChild;
+ ScrollBar *m_pScrollBar;
+ int m_iSliderPos;
+ bool m_bCloseOnUp;
+ bool m_bMouseWasDown;
+ int m_aClickPos[2];
+ CExButton *m_pItemWikiPageButton;
+ CNavigationPanel *m_pTeamNavPanel;
+ CExButton *m_pPreviewButton;
+ CExImageButton *m_pGoFullscreenButton;
+ int m_nViewMaxHeight;
+
+ CFullscreenStorePreviewItem *m_pFullscreenPanel;
+ bool m_bIsHalloweenOrFullmoonOnlyItem;
+
+ CEconItemView *m_pItemViewData;
+ CEconItem *m_pSOEconItemData;
+
+ // mouse over reference item tooltip
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+ CUtlVector< CItemModelPanel* > m_vecReferenceItemPanels;
+
+ CPanelAnimationVarAliasType( int, m_iSmallVerticalBreakSize, "small_vertical_break_size", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iMediumVerticalBreakSize, "medium_vertical_break_size", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iBigVerticalBreakSize, "big_vertical_break_size", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iHorizontalBreakSize, "horizontal_break_size", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iControlButtonWidth, "control_button_width", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iControlButtonHeight, "control_button_height", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iControlButtonY, "control_button_y", "0", "proportional_ypos" );
+
+ MESSAGE_FUNC_INT( OnSliderMoved, "ScrollBarSliderMoved", position );
+};
+
+#endif // TF_STORE_PREVIEW_ITEM2_H
diff --git a/game/client/tf/vgui/strange_count_transfer_panel.cpp b/game/client/tf/vgui/strange_count_transfer_panel.cpp
new file mode 100644
index 0000000..d16a941
--- /dev/null
+++ b/game/client/tf/vgui/strange_count_transfer_panel.cpp
@@ -0,0 +1,269 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "strange_count_transfer_panel.h"
+#include "cdll_client_int.h"
+#include "ienginevgui.h"
+#include "econ_item_tools.h"
+#include "econ_ui.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStatModuleItemSelectionPanel : public CItemCriteriaSelectionPanel
+{
+ DECLARE_CLASS_SIMPLE( CStatModuleItemSelectionPanel, CItemCriteriaSelectionPanel );
+public:
+ CStatModuleItemSelectionPanel( Panel *pParent, const CEconItemView* pCorrespondingItem )
+ : BaseClass( pParent, NULL )
+ , m_pCorrespondingItem( pCorrespondingItem )
+ , m_mapXifierClassCount( CaselessStringLessThan )
+ {
+ int nCount = InventoryManager()->GetLocalInventory()->GetItemCount();
+ for( int i=0; i<nCount; ++i )
+ {
+ if ( !BIsItemStrange( InventoryManager()->GetLocalInventory()->GetItem( i ) ) )
+ continue;
+
+ const char *pItemXifier = InventoryManager()->GetLocalInventory()->GetItem( i )->GetItemDefinition()->GetXifierRemapClass();
+ auto idx = m_mapXifierClassCount.Find( pItemXifier );
+ if ( idx == m_mapXifierClassCount.InvalidIndex() )
+ {
+ idx = m_mapXifierClassCount.Insert( pItemXifier, 0 );
+ }
+
+ m_mapXifierClassCount[ idx ] = m_mapXifierClassCount[ idx ] + 1;
+ }
+ }
+
+ void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ vgui::Label* pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemSlotLabel") );
+ if ( pWeaponLabel )
+ {
+ pWeaponLabel->SetVisible( false );
+ }
+
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const
+ {
+ if ( !pItem )
+ return NULL;
+
+ if ( !BIsItemStrange( pItem ) )
+ return "#TF_StrangeCount_Transfer_NotStrange";
+
+ if ( m_pCorrespondingItem )
+ {
+ if ( pItem->GetItemID() == m_pCorrespondingItem->GetItemID() )
+ return "#TF_StrangeCount_Transfer_Self";
+
+ if ( !CEconTool_StrangeCountTransfer::AreItemsEligibleForStrangeCountTransfer( m_pCorrespondingItem, pItem ) )
+ return "#TF_StrangeCount_Transfer_TypeMismatch";
+ }
+
+ const char *pItemXifier = pItem->GetItemDefinition()->GetXifierRemapClass();
+ auto idx = m_mapXifierClassCount.Find( pItemXifier );
+ int nCount = 0;
+ if ( !pItemXifier )
+ {
+ // if no xifier, find atleast 1 other matching item
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( pInventory )
+ {
+ for ( int i = 0; i < pInventory->GetItemCount(); i++ )
+ {
+ CEconItemView *pIterItem = pInventory->GetItem( i );
+ if ( pIterItem->GetItemDefIndex() == pItem->GetItemDefIndex() && BIsItemStrange(pIterItem) )
+ {
+ // find 2 or more, yourself and another
+ if ( ++nCount >= 2 )
+ return NULL;
+ }
+ }
+ }
+ }
+ else if ( idx != m_mapXifierClassCount.InvalidIndex() )
+ {
+ nCount = m_mapXifierClassCount[ idx ];
+ }
+
+ if ( nCount < 2 )
+ {
+ return "#TF_StrangeCount_Transfer_NotEnoughMatches";
+ }
+
+ return NULL;
+ }
+
+protected:
+ const char * m_pszTitleToken;
+ const CEconItemView* m_pCorrespondingItem;
+ CUtlMap< const char*, int > m_mapXifierClassCount;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStrangeCountTransferPanel::CStrangeCountTransferPanel( vgui::Panel *parent, CEconItemView* pToolItem )
+ : BaseClass( parent, "StrangeCountTrasnferDialog" )
+ , m_pToolItem( pToolItem )
+{
+ Assert( pToolItem );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_hSelectionPanel = 0;
+ m_pSelectingItemModelPanel = NULL;
+
+ EditablePanel* pBG = new EditablePanel( this, "BG" );
+
+ m_pSourceStrangeModelPanel = new CItemModelPanel( pBG, "SourceItem" );
+ m_pSourceStrangeModelPanel->SetActAsButton( true, true );
+ m_pTargetStrangeModelPanel = new CItemModelPanel( pBG, "TargetItem" );
+ m_pTargetStrangeModelPanel->SetActAsButton( true, true );
+ m_pOKButton = new CExButton( pBG, "OkButton", "" );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" );
+ SetScheme( scheme );
+ SetProportional( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStrangeCountTransferPanel::~CStrangeCountTransferPanel( void )
+{
+ if ( m_hSelectionPanel )
+ {
+ m_hSelectionPanel->MarkForDeletion();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStrangeCountTransferPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetResFile() );
+}
+
+void CStrangeCountTransferPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ UpdateOKButton();
+
+ m_pSourceStrangeModelPanel->SetTooltip( EconUI()->GetBackpackPanel()->GetMouseOverToolTipPanel(), "" );
+ m_pTargetStrangeModelPanel->SetTooltip( EconUI()->GetBackpackPanel()->GetMouseOverToolTipPanel(), "" );
+}
+
+void CStrangeCountTransferPanel::OnCommand( const char *command )
+{
+ if( FStrEq( "apply", command ) )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyStrangeCountTransfer> msg( k_EMsgGCApplyStrangeCountTransfer );
+
+ if ( !m_pToolItem || !m_pSourceStrangeModelPanel->GetItem() || !m_pTargetStrangeModelPanel->GetItem() )
+ return;
+
+ msg.Body().set_tool_item_id( m_pToolItem->GetItemID() );
+ msg.Body().set_item_src_item_id( m_pSourceStrangeModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_item_dest_item_id( m_pTargetStrangeModelPanel->GetItem()->GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolItem, "applied_strangecounttransfer", m_pToolItem->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+
+ SetVisible( false );
+ MarkForDeletion();
+
+ return;
+ }
+ else if ( FStrEq( "cancel", command ) )
+ {
+ MarkForDeletion();
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CStrangeCountTransferPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "gameui_hidden" ) )
+ {
+ SetVisible( false );
+ MarkForDeletion();
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStrangeCountTransferPanel::OnItemPanelMousePressed( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() )
+ {
+ m_pSelectingItemModelPanel = pItemPanel;
+
+ CEconItemView* pOtherItem = pItemPanel == m_pSourceStrangeModelPanel ? m_pTargetStrangeModelPanel->GetItem()
+ : m_pSourceStrangeModelPanel->GetItem();
+
+ m_hSelectionPanel = new CStatModuleItemSelectionPanel( GetParent(), pOtherItem );
+
+ // Clicked on an item in the crafting area. Open up the selection panel.
+ m_hSelectionPanel->ShowDuplicateCounts( false );
+ m_hSelectionPanel->ShowPanel( 0, true );
+ m_hSelectionPanel->SetCaller( this );
+ m_hSelectionPanel->SetZPos( GetZPos() + 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStrangeCountTransferPanel::OnSelectionReturned( KeyValues *data )
+{
+ Assert( m_pSelectingItemModelPanel );
+
+ if ( data && m_pSelectingItemModelPanel )
+ {
+ uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );
+
+ CEconItemView* pSelectedItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( ulIndex );
+ m_pSelectingItemModelPanel->SetItem( pSelectedItem );
+ }
+
+ UpdateOKButton();
+
+ m_pSelectingItemModelPanel = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStrangeCountTransferPanel::UpdateOKButton()
+{
+ bool bOKEnabled = m_pSourceStrangeModelPanel->GetItem() && m_pTargetStrangeModelPanel->GetItem();
+ m_pOKButton->SetEnabled( bOKEnabled );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/strange_count_transfer_panel.h b/game/client/tf/vgui/strange_count_transfer_panel.h
new file mode 100644
index 0000000..912260e
--- /dev/null
+++ b/game/client/tf/vgui/strange_count_transfer_panel.h
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STRANGE_COUNT_TRANSFER_H
+#define STRANGE_COUNT_TRANSFER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "backpack_panel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "tf_gcmessages.h"
+#include "econ_gcmessages.h"
+#include "tf_imagepanel.h"
+#include "tf_controls.h"
+#include "item_selection_panel.h"
+#include "confirm_dialog.h"
+
+//-----------------------------------------------------------------------------
+// A panel to let users choose 2 weapons to tranfer strange counts with
+//-----------------------------------------------------------------------------
+class CStrangeCountTransferPanel : public vgui::EditablePanel, public CGameEventListener
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStrangeCountTransferPanel, vgui::EditablePanel );
+ CStrangeCountTransferPanel( vgui::Panel *parent, CEconItemView* pToolItem );
+ ~CStrangeCountTransferPanel( void );
+
+ virtual const char *GetResFile( void ) { return "Resource/UI/econ/StrangeCountTransferDialog.res"; }
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout() OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel );
+ MESSAGE_FUNC_PARAMS( OnSelectionReturned, "SelectionReturned", data );
+
+private:
+
+ void UpdateOKButton();
+
+ CTFTextToolTip *m_pToolTip;
+ vgui::EditablePanel *m_pToolTipEmbeddedPanel;
+
+ DHANDLE<CItemCriteriaSelectionPanel> m_hSelectionPanel;
+
+ CExButton *m_pOKButton;
+ CEconItemView *m_pToolItem;
+ CItemModelPanel *m_pSelectingItemModelPanel;
+ CItemModelPanel *m_pSourceStrangeModelPanel;
+ CItemModelPanel *m_pTargetStrangeModelPanel;
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+};
+
+#endif // STRANGE_COUNT_TRANSFER
diff --git a/game/client/tf/vgui/testitem_dialog.cpp b/game/client/tf/vgui/testitem_dialog.cpp
new file mode 100644
index 0000000..47b6fae
--- /dev/null
+++ b/game/client/tf/vgui/testitem_dialog.cpp
@@ -0,0 +1,754 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/CheckButton.h"
+#include "testitem_dialog.h"
+#include "tf_controls.h"
+#include "c_playerresource.h"
+#include "gcsdk/gcmsg.h"
+#include "tf_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_gcmessages.h"
+#include "ienginevgui.h"
+#include "filesystem.h"
+#include "vgui_controls/FileOpenDialog.h"
+#include "econ_item_system.h"
+#include "testitem_root.h"
+#include "econ_item_tools.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern const char *g_TeamVisualSections[TEAM_VISUAL_SECTIONS];
+
+static const char *g_pszTestItemHideBodygroup[] =
+{
+ "hat", // TI_HIDEBG_HAT,
+ "headphones", // TI_HIDEBG_HEADPHONES,
+ "medal", // TI_HIDEBG_MEDALS,
+ "grenades", // TI_HIDEBG_GRENADES,
+ "bullets", // TI_HIDEBG_BULLETS
+ "arrows", // TI_HIDEBG_ARROWS
+ "rightarm", // TI_HIDEBG_RIGHTARM
+ "shoes_socks", // TI_HIDEBG_SHOES_SOCKS
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_pszTestItemHideBodygroup ) == TI_HIDEBG_COUNT );
+
+static const char *g_pszClassSubdirectories[] =
+{
+ "all_class", // TF_CLASS_UNDEFINED = 0,
+ "scout", // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS
+ "sniper", // TF_CLASS_SNIPER,
+ "soldier", // TF_CLASS_SOLDIER,
+ "demo", // TF_CLASS_DEMOMAN,
+ "medic", // TF_CLASS_MEDIC,
+ "heavy", // TF_CLASS_HEAVYWEAPONS,
+ "pyro", // TF_CLASS_PYRO,
+ "spy", // TF_CLASS_SPY,
+ "engineer", // TF_CLASS_ENGINEER,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTestItemDialog::CTestItemDialog( vgui::Panel *parent, testitem_itemtypes_t iItemType, int iClassUsage, KeyValues *pExistingKVs ) : vgui::EditablePanel( parent, "TestItemDialog" )
+{
+ // Need to use the clientscheme (we're not parented to a clientscheme'd panel)
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_hImportModelDialog = NULL;
+ m_pModelLabel = NULL;
+ m_pSelectModelLabel = NULL;
+ m_pNoItemsToReplaceLabel = NULL;
+ m_pSelectModelButton = NULL;
+ m_pOkButton = NULL;
+
+ m_pItemReplacedPanel = new vgui::EditablePanel( this, "ItemReplacedPanel" );
+ m_pItemReplacedComboBox = new vgui::ComboBox( m_pItemReplacedPanel, "ItemReplacedComboBox", 20, false );
+ m_pItemReplacedComboBox->AddActionSignalTarget( this );
+
+ m_pExistingItemToTestPanel = new vgui::EditablePanel( this, "ExistingItemToTestPanel" );
+ m_pExistingItemComboBox = new vgui::ComboBox( m_pExistingItemToTestPanel, "ExistingItemComboBox", 20, false );
+ m_pExistingItemComboBox->AddActionSignalTarget( this );
+
+ m_pBodygroupPanel = new vgui::EditablePanel( this, "BodygroupPanel" );
+ for ( int i = 0; i < TI_HIDEBG_COUNT; i++ )
+ {
+ m_pBodygroupCheckButtons[i] = new vgui::CheckButton( m_pBodygroupPanel, VarArgs("HideBodygroupCheckBox%d",i), "" );
+ m_pBodygroupCheckButtons[i]->AddActionSignalTarget( this );
+ }
+
+ m_pCustomizationsPanel = new vgui::EditablePanel( this, "CustomizationsPanel" );
+ m_pPaintColorComboBox = new vgui::ComboBox( m_pCustomizationsPanel, "PaintColorComboBox", 20, false );
+ m_pPaintColorComboBox->AddActionSignalTarget( this );
+
+ m_pUnusualEffectComboBox = new vgui::ComboBox( m_pCustomizationsPanel, "UnusualEffectComboBox", 20, false );
+ m_pUnusualEffectComboBox->AddActionSignalTarget( this );
+
+ m_iItemType = iItemType;
+ m_iClassUsage = iClassUsage;
+
+ m_szRelativePath[0] = '\0';
+ SetDialogVariable("testmodel", g_pVGuiLocalize->Find( "#IT_NoModel" ) );
+ SetEntryStep( TI_STEP_MODELNAME );
+
+ // Load our scheme right away so we have all our pieces ready
+ MakeReadyForUse();
+
+ SetupPaintColorComboBox();
+ SetupUnusualEffectComboBox();
+
+ // Pull the data out of the existing KVs
+ if ( pExistingKVs )
+ {
+ InitializeFromExistingKVs( pExistingKVs );
+ }
+ else
+ {
+ for ( int i = 0; i < TI_HIDEBG_COUNT; i++ )
+ {
+ // Start with the "hat" bodygroup checked (for non-weapons)
+ bool bIsHat = ( m_iItemType != TI_TYPE_WEAPON ) && ( i == 0 );
+ m_pBodygroupCheckButtons[i]->SetSelected( bIsHat );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::InitializeFromExistingKVs( KeyValues *pExistingKVs )
+{
+ // If we're testing an existing item, it supercedes everything else
+ item_definition_index_t iExistingItemDef = pExistingKVs->GetInt( "existing_itemdef", INVALID_ITEM_DEF_INDEX );
+ if ( iExistingItemDef != INVALID_ITEM_DEF_INDEX )
+ {
+ SetupItemComboBox( m_pExistingItemComboBox );
+
+ // Loop through the entries until we find the specified item def
+ for ( int i = 0; i < m_pExistingItemComboBox->GetItemCount(); i++ )
+ {
+ int iItemID = m_pExistingItemComboBox->GetItemIDFromRow(i);
+ KeyValues *pRowKV = m_pExistingItemComboBox->GetItemUserData( iItemID );
+ if ( pRowKV && pRowKV->GetInt( "item", INVALID_ITEM_DEF_INDEX ) == iExistingItemDef )
+ {
+ m_pExistingItemComboBox->SilentActivateItemByRow(i);
+ SetEntryStep( TI_STEP_FINISHED );
+ }
+ }
+ }
+ else
+ {
+ const char *pszModel = pExistingKVs->GetString( "model_player", NULL );
+ if ( pszModel && pszModel[0] )
+ {
+ Q_strncpy( m_szRelativePath, pszModel, MAX_PATH );
+ SetDialogVariable("testmodel", m_szRelativePath );
+ SetEntryStep( TI_STEP_MODELNAME );
+
+ SetEntryStep( TI_STEP_WPN_ITEMREPLACED );
+ if ( m_iItemType == TI_TYPE_WEAPON )
+ {
+ item_definition_index_t iItemDefToReplace = pExistingKVs->GetInt( "item_replace", INVALID_ITEM_DEF_INDEX );
+ if ( iItemDefToReplace != INVALID_ITEM_DEF_INDEX )
+ {
+ SetupItemComboBox( m_pItemReplacedComboBox );
+
+ // Loop through the entries until we find the specified item def
+ for ( int i = 0; i < m_pItemReplacedComboBox->GetItemCount(); i++ )
+ {
+ int iItemID = m_pItemReplacedComboBox->GetItemIDFromRow(i);
+ KeyValues *pRowKV = m_pItemReplacedComboBox->GetItemUserData( iItemID );
+ if ( pRowKV && pRowKV->GetInt( "item", INVALID_ITEM_DEF_INDEX ) == iItemDefToReplace )
+ {
+ m_pItemReplacedComboBox->SilentActivateItemByRow(i);
+ SetEntryStep( TI_STEP_FINISHED );
+ }
+ }
+ }
+ }
+ else
+ {
+ KeyValues *pkvVisuals = pExistingKVs->FindKey( g_TeamVisualSections[0] );
+ if ( pkvVisuals )
+ {
+ KeyValues *pKVEntry = pkvVisuals->GetFirstSubKey();
+ while ( pKVEntry )
+ {
+ if ( !Q_stricmp( pKVEntry->GetName(), "player_bodygroups" ) )
+ {
+ FOR_EACH_SUBKEY( pKVEntry, pKVSubEntry )
+ {
+ int iBG = StringFieldToInt( pKVSubEntry->GetName(), g_pszTestItemHideBodygroup, ARRAYSIZE(g_pszTestItemHideBodygroup) );
+ if ( iBG >= 0 && iBG < TI_HIDEBG_COUNT )
+ {
+ m_pBodygroupCheckButtons[iBG]->SetSelected( pKVSubEntry->GetInt() == 0 );
+ }
+ }
+ }
+
+ pKVEntry = pKVEntry->GetNextKey();
+ }
+ }
+
+ // Start with the right paint can selected
+ int iPaintCanIndex = pExistingKVs->GetInt("paintcan_index", 0);
+ for ( int i = 0; i < m_pPaintColorComboBox->GetItemCount(); i++ )
+ {
+ int iItemID = m_pPaintColorComboBox->GetItemIDFromRow(i);
+ KeyValues *pRowKV = m_pPaintColorComboBox->GetItemUserData( iItemID );
+ if ( pRowKV && pRowKV->GetInt("paintcan_index",0) == iPaintCanIndex )
+ {
+ m_pPaintColorComboBox->SilentActivateItemByRow(i);
+ }
+ }
+
+ // Start with the right unusual effect selected
+ int iUnusualIndex = pExistingKVs->GetInt("unusual_index", 0);
+ for ( int i = 0; i < m_pUnusualEffectComboBox->GetItemCount(); i++ )
+ {
+ int iItemID = m_pUnusualEffectComboBox->GetItemIDFromRow(i);
+ KeyValues *pRowKV = m_pUnusualEffectComboBox->GetItemUserData( iItemID );
+ if ( pRowKV && pRowKV->GetInt("unusual_index",0) == iUnusualIndex )
+ {
+ m_pUnusualEffectComboBox->SilentActivateItemByRow(i);
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTestItemDialog::~CTestItemDialog( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/TestItemDialog.res" );
+
+ m_pModelLabel = dynamic_cast<CExLabel*>( FindChildByName( "ModelLabel" ) );
+ m_pSelectModelLabel = dynamic_cast<CExLabel*>( FindChildByName( "SelectModelLabel" ) );
+ m_pSelectModelButton = dynamic_cast<CExButton*>( FindChildByName( "SelectModelButton" ) );
+ m_pOkButton = dynamic_cast<CExButton*>( FindChildByName( "OkButton" ) );
+
+ m_pNoItemsToReplaceLabel = dynamic_cast<CExLabel*>( m_pItemReplacedPanel->FindChildByName( "NoItemsToReplaceLabel" ) );
+
+ SetEntryStep( m_iEntryStep );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::FireGameEvent( IGameEvent *event )
+{
+ const char *type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ Close();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::Close( void )
+{
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::CloseAndUpdateItem( void )
+{
+ // We're going to assemble a KV block that describes this test item
+ KeyValues *kv = new KeyValues( "SetTestItemKVs" );
+ kv->SetInt( "item_type", m_iItemType );
+ kv->SetString( "model_player", m_szRelativePath );
+ kv->SetBool( "test_existing_item", false );
+ kv->SetInt( "attach_to_hands", (m_iItemType == TI_TYPE_WEAPON) );
+
+ KeyValues *pKVModels = new KeyValues( "model_player_per_class" );
+ kv->AddSubKey( pKVModels );
+ const char *pFilename = V_UnqualifiedFileName( m_szRelativePath );
+ if ( pFilename)
+ {
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < ARRAYSIZE( g_pszClassSubdirectories ); i++ )
+ {
+ if ( m_iClassUsage == 1 || ( m_iClassUsage & (1 << i) ) )
+ {
+ CFmtStr1024 path( "models/player/items/%s/%s", g_pszClassSubdirectories[i], pFilename );
+ if ( g_pFullFileSystem->FileExists( path.Access() ) )
+ {
+ pKVModels->SetString( ItemSystem()->GetItemSchema()->GetClassUsabilityStrings()[i], path.Access() );
+ }
+ }
+ }
+ }
+
+ KeyValues *pkvVisuals = new KeyValues( g_TeamVisualSections[0] ),
+ *pkvPlayerBodyGroups = new KeyValues( "player_bodygroups" );
+
+ kv->AddSubKey( pkvVisuals );
+ pkvVisuals->AddSubKey( pkvPlayerBodyGroups );
+
+ for ( int i = 0; i < TI_HIDEBG_COUNT; i++ )
+ {
+ KeyValues *pKVBG = new KeyValues( g_pszTestItemHideBodygroup[i] );
+ pKVBG->SetInt( NULL, m_pBodygroupCheckButtons[i]->IsSelected() ? 0 : 1 );
+ pkvPlayerBodyGroups->AddSubKey( pKVBG );
+ }
+
+ // Extract the paint can index
+ KeyValues *pPaintComboKV = m_pPaintColorComboBox->GetActiveItemUserData();
+ int iPaintCanIndex = pPaintComboKV ? pPaintComboKV->GetInt( "paintcan_index", 0 ) : 0;
+ kv->SetInt( "paintcan_index", iPaintCanIndex );
+
+ // Extract the unusual effect index
+ KeyValues *pUnusualComboKV = m_pUnusualEffectComboBox->GetActiveItemUserData();
+ int iUnusualIndex = pUnusualComboKV ? pUnusualComboKV->GetInt( "unusual_index", 0 ) : 0;
+ kv->SetInt( "unusual_index", iUnusualIndex );
+
+ item_definition_index_t iItemDef = INVALID_ITEM_DEF_INDEX;
+
+ // See if we're copying an existing item
+ KeyValues *pExistingUserData = m_pExistingItemComboBox->GetActiveItemUserData();
+ item_definition_index_t iExistingItemDef = pExistingUserData ? pExistingUserData->GetInt( "item", INVALID_ITEM_DEF_INDEX ) : INVALID_ITEM_DEF_INDEX;
+ if ( iExistingItemDef != INVALID_ITEM_DEF_INDEX )
+ {
+ iItemDef = iExistingItemDef;
+ kv->SetInt( "existing_itemdef", iItemDef );
+ kv->SetBool( "test_existing_item", true );
+
+ // copy model path from existing items
+ GameItemDefinition_t *pItemDef = ItemSystem()->GetStaticDataForItemByDefIndex( iItemDef );
+ if ( pItemDef )
+ {
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( m_iClassUsage == 1 || ( m_iClassUsage & (1 << iClass) ) )
+ {
+ const char *pszClassString = ItemSystem()->GetItemSchema()->GetClassUsabilityStrings()[iClass];
+ const char *pszModel = pItemDef->GetPlayerDisplayModel( iClass );
+ pKVModels->SetString( pszClassString, pszModel );
+ }
+ }
+ }
+ }
+ else
+ {
+ KeyValues *pUserData = m_pItemReplacedComboBox->GetActiveItemUserData();
+ iItemDef = pUserData ? pUserData->GetInt( "item", INVALID_ITEM_DEF_INDEX ) : INVALID_ITEM_DEF_INDEX;
+
+ // Find the item def we're going to build off
+ switch ( m_iItemType )
+ {
+ case TI_TYPE_WEAPON:
+ // Need an item def to replace
+ if ( iItemDef == INVALID_ITEM_DEF_INDEX )
+ return;
+ break;
+ case TI_TYPE_HEADGEAR:
+ iItemDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName("Football Helmet")->GetDefinitionIndex();
+ break;
+ case TI_TYPE_MISC1:
+ iItemDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName("Employee Badge A")->GetDefinitionIndex();
+ break;
+ case TI_TYPE_MISC2:
+ iItemDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName("High Five Taunt")->GetDefinitionIndex();
+ break;
+ }
+ }
+
+ // Tell the server what item we're replacing, and what def index we used
+ kv->SetInt( "item_replace", iItemDef );
+
+ // Send it to the testing root panel
+ PostMessage( GetParent(), kv );
+
+ Close();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "cancel" ) )
+ {
+ Close();
+ return;
+ }
+ else if ( !Q_stricmp( command, "ok" ) )
+ {
+ CloseAndUpdateItem();
+ return;
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( false, true );
+ return;
+ }
+ else if ( !Q_stricmp( command, "select_model" ) )
+ {
+ OpenSelectModelDialog();
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::OpenSelectModelDialog( void )
+{
+ if (m_hImportModelDialog == NULL)
+ {
+ m_hImportModelDialog = new vgui::FileOpenDialog( NULL, "#ToolCustomizeTextureTitle", true );
+ m_hImportModelDialog->AddFilter( "*.mdl", "#IT_MDL_Files", true );
+ m_hImportModelDialog->AddActionSignalTarget( this );
+ }
+
+ char szModelsDir[MAX_PATH];
+ switch( m_iItemType )
+ {
+ default:
+ break;
+
+ case TI_TYPE_WEAPON:
+ m_hImportModelDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( "models/weapons/c_models", "MOD", szModelsDir, sizeof(szModelsDir) ) );
+ break;
+
+ case TI_TYPE_HEADGEAR:
+ case TI_TYPE_MISC1:
+ case TI_TYPE_MISC2:
+ {
+ const char *pszSubDir = NULL;
+
+ // All classes?
+ if ( m_iClassUsage == 1 )
+ {
+ pszSubDir = g_pszClassSubdirectories[0];
+ }
+ else
+ {
+ // If we only have one class, jump into that directory
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < LOADOUT_COUNT; i++ )
+ {
+ if ( m_iClassUsage & (1 << i) )
+ {
+ if ( !pszSubDir )
+ {
+ pszSubDir = g_pszClassSubdirectories[i];
+ }
+ else
+ {
+ // Found multiple classes. Move back up to the base dir.
+ pszSubDir = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( pszSubDir )
+ {
+ m_hImportModelDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( VarArgs("models/player/items/%s",pszSubDir), "MOD", szModelsDir, sizeof(szModelsDir) ) );
+ }
+ else
+ {
+ m_hImportModelDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( "models/player/items", "MOD", szModelsDir, sizeof(szModelsDir) ) );
+ }
+ }
+ break;
+ }
+
+ m_hImportModelDialog->DoModal( false );
+ m_hImportModelDialog->Activate();
+
+ // Base file dialog won't refresh if it's opening to the same directory it was in. Force it to.
+ PostMessage( m_hImportModelDialog->GetVPanel(), new KeyValues( "PopulateFileList" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+struct ComboBoxTestItem_t
+{
+ const wchar_t *pwszItemName;
+ item_definition_index_t itemDef;
+};
+static int SortComboBoxTestItem( const ComboBoxTestItem_t *a, const ComboBoxTestItem_t *b )
+{
+ return V_wcscmp( a->pwszItemName, b->pwszItemName );
+}
+
+void CTestItemDialog::SetupItemComboBox( vgui::ComboBox *pComboBox )
+{
+ pComboBox->RemoveAll();
+
+ CUtlVector<item_definition_index_t> vecDefs;
+ int iReplacements = ((CTestItemRoot*)GetParent())->FindReplaceableItemsForSelectedClass( &vecDefs, m_iItemType == TI_TYPE_WEAPON );
+ if ( iReplacements )
+ {
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "item", INVALID_ITEM_DEF_INDEX );
+ pComboBox->AddItem( "#IT_ItemReplaced_Select", pKeyValues );
+
+ CUtlVector< ComboBoxTestItem_t > testItems;
+ FOR_EACH_VEC( vecDefs, i )
+ {
+ CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( vecDefs[i] );
+ if ( pDef )
+ {
+ const wchar_t *pwszLocalizedItemName = g_pVGuiLocalize->Find( pDef->GetItemBaseName() );
+ if ( pwszLocalizedItemName )
+ {
+ int newIndex = testItems.AddToTail();
+ testItems[newIndex].itemDef = vecDefs[i];
+ testItems[newIndex].pwszItemName = pwszLocalizedItemName;
+ }
+ }
+ }
+
+ if ( testItems.Count() )
+ {
+ testItems.Sort( &SortComboBoxTestItem );
+ FOR_EACH_VEC( testItems, i )
+ {
+ pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "item", testItems[i].itemDef );
+ pComboBox->AddItem( testItems[i].pwszItemName, pKeyValues );
+ }
+ }
+ }
+
+ // No valid entries?
+ if ( pComboBox == m_pItemReplacedComboBox )
+ {
+ if ( m_pNoItemsToReplaceLabel )
+ {
+ m_pNoItemsToReplaceLabel->SetVisible( !iReplacements );
+ }
+ m_pItemReplacedPanel->SetVisible( iReplacements );
+ }
+
+ pComboBox->SetItemEnabled( 0, false );
+ pComboBox->SilentActivateItemByRow( 0 );
+ pComboBox->GetMenu()->SetBgColor( Color(0,0,0,255) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::SetupPaintColorComboBox( void )
+{
+ m_pPaintColorComboBox->RemoveAll();
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "paintcan_index", 0 );
+ m_pPaintColorComboBox->AddItem( "#IT_PaintNone", pKeyValues );
+
+ // Now loop through all our paints and add them to the list
+ const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap();
+ FOR_EACH_MAP( mapItemDefs, i )
+ {
+ const CEconItemDefinition *pDef = mapItemDefs[i];
+
+ const CEconTool_PaintCan *pEconToolPaintCan = pDef->GetTypedEconTool<CEconTool_PaintCan>();
+ if ( !pEconToolPaintCan )
+ continue;
+
+ pKeyValues->SetInt( "paintcan_index", pDef->GetDefinitionIndex() );
+ m_pPaintColorComboBox->AddItem( g_pVGuiLocalize->Find( pDef->GetItemBaseName() ), pKeyValues );
+
+ // Make sure it has valid colors (to skip the store version of the paint can)
+ KeyValues *pAttribs = pDef->GetDefinitionKey( "attributes" );
+ if ( !pAttribs )
+ continue;
+
+ KeyValues *pRGBAttrib = pAttribs->FindKey( "set_item_tint_rgb" );
+ if ( !pRGBAttrib )
+ continue;
+
+ int iModifiedRGB = pRGBAttrib->GetInt( "value", -1 );
+ if ( iModifiedRGB != -1 )
+ {
+ m_pPaintColorComboBox->AddItem( g_pVGuiLocalize->Find( pDef->GetItemBaseName() ), pKeyValues );
+ }
+ }
+
+ m_pPaintColorComboBox->SilentActivateItemByRow( 0 );
+ m_pPaintColorComboBox->GetMenu()->SetBgColor( Color(0,0,0,255) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::SetupUnusualEffectComboBox( void )
+{
+ m_pUnusualEffectComboBox->RemoveAll();
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "unusual_index", 0 );
+ m_pUnusualEffectComboBox->AddItem( "#IT_UnusualNone", pKeyValues );
+
+ // Now loop through all unusual effects and add them to the list.
+ const CEconItemSchema::ParticleDefinitionMap_t& mapParticleDefs = ItemSystem()->GetItemSchema()->GetAttributeControlledParticleSystems();
+ FOR_EACH_MAP( mapParticleDefs, i )
+ {
+ pKeyValues->SetInt( "unusual_index", mapParticleDefs[i].nSystemID );
+
+ char particleNameEntry[128];
+ Q_snprintf( particleNameEntry, ARRAYSIZE( particleNameEntry ), "#Attrib_Particle%i", mapParticleDefs[i].nSystemID );
+ m_pUnusualEffectComboBox->AddItem( g_pVGuiLocalize->Find( particleNameEntry ), pKeyValues );
+ }
+
+ m_pUnusualEffectComboBox->SilentActivateItemByRow( 0 );
+ m_pUnusualEffectComboBox->GetMenu()->SetBgColor( Color(0,0,0,255) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::SetEntryStep( testitem_entrysteps_t iStep )
+{
+ // Skip over the item replacement if we're not a weapon
+ if ( iStep == TI_STEP_WPN_ITEMREPLACED && m_iItemType != TI_TYPE_WEAPON )
+ {
+ iStep = (testitem_entrysteps_t)(iStep+1);
+ }
+
+ if ( iStep == TI_STEP_NONWPN_BODYGROUPS || iStep == TI_STEP_OTHER_OPTIONS )
+ {
+ // Move to "finished" straight away
+ iStep = TI_STEP_FINISHED;
+ }
+
+ m_iEntryStep = iStep;
+
+ if ( m_pSelectModelButton )
+ {
+ m_pSelectModelButton->SetVisible( iStep >= TI_STEP_MODELNAME );
+ m_pSelectModelLabel->SetVisible( iStep >= TI_STEP_MODELNAME );
+ m_pModelLabel->SetVisible( iStep >= TI_STEP_MODELNAME );
+ }
+
+ bool bTestingExistingItem = (iStep > TI_STEP_MODELNAME && m_szRelativePath[0] == '\0');
+
+ m_pBodygroupPanel->SetVisible( iStep >= TI_STEP_NONWPN_BODYGROUPS && m_iItemType != TI_TYPE_WEAPON && !bTestingExistingItem );
+ m_pExistingItemToTestPanel->SetVisible( iStep == TI_STEP_MODELNAME || bTestingExistingItem );
+ m_pItemReplacedPanel->SetVisible( iStep >= TI_STEP_WPN_ITEMREPLACED && m_iItemType == TI_TYPE_WEAPON && !bTestingExistingItem );
+ if ( m_pNoItemsToReplaceLabel )
+ {
+ m_pNoItemsToReplaceLabel->SetVisible( false );
+ }
+
+ m_pCustomizationsPanel->SetVisible( (iStep >= TI_STEP_CUSTOMIZATION && m_iItemType != TI_TYPE_WEAPON) );
+
+ if ( m_pOkButton )
+ {
+ m_pOkButton->SetEnabled( m_iEntryStep >= TI_STEP_FINISHED );
+ }
+
+ switch ( m_iEntryStep )
+ {
+ case TI_STEP_MODELNAME:
+ if ( !m_szRelativePath[0] )
+ {
+ SetDialogVariable("testmodel", g_pVGuiLocalize->Find( "#IT_NoModel" ) );
+ }
+ SetupItemComboBox( m_pExistingItemComboBox );
+ break;
+ case TI_STEP_WPN_ITEMREPLACED:
+ SetupItemComboBox( m_pItemReplacedComboBox );
+ break;
+ case TI_STEP_NONWPN_BODYGROUPS:
+ break;
+
+ default:
+ case TI_STEP_FINISHED:
+ break;
+ }
+
+ SetDialogVariable( "testtitle", g_pVGuiLocalize->Find( VarArgs("#IT_Title_%d",m_iItemType) ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ if ( pPanel == m_pExistingItemComboBox )
+ {
+ if ( m_iItemType != TI_TYPE_WEAPON )
+ {
+ SetEntryStep( TI_STEP_OTHER_OPTIONS );
+ }
+ else
+ {
+ SetEntryStep( TI_STEP_FINISHED );
+ }
+ }
+ else if ( pPanel == m_pItemReplacedComboBox )
+ {
+ SetEntryStep( TI_STEP_FINISHED );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemDialog::OnFileSelected(const char *fullpath)
+{
+ m_szRelativePath[0] = '\0';
+ if ( g_pFullFileSystem->FullPathToRelativePathEx( fullpath, "GAME", m_szRelativePath, sizeof(m_szRelativePath) ) )
+ {
+ Q_FixSlashes( m_szRelativePath, '/' );
+ SetDialogVariable("testmodel", m_szRelativePath );
+ SetEntryStep( TI_STEP_WPN_ITEMREPLACED );
+ }
+ else
+ {
+ SetDialogVariable("testmodel", g_pVGuiLocalize->Find( "#IT_NoModel" ) );
+ }
+
+ // Nuke the file open dialog
+ m_hImportModelDialog->MarkForDeletion();
+ m_hImportModelDialog = NULL;
+}
+
diff --git a/game/client/tf/vgui/testitem_dialog.h b/game/client/tf/vgui/testitem_dialog.h
new file mode 100644
index 0000000..ca7a518
--- /dev/null
+++ b/game/client/tf/vgui/testitem_dialog.h
@@ -0,0 +1,99 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TESTITEM_DIALOG_H
+#define TESTITEM_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "tf_controls.h"
+
+enum testitem_entrysteps_t
+{
+ TI_STEP_MODELNAME,
+ TI_STEP_WPN_ITEMREPLACED,
+ TI_STEP_NONWPN_BODYGROUPS,
+ TI_STEP_OTHER_OPTIONS,
+ TI_STEP_CUSTOMIZATION,
+
+ TI_STEP_FINISHED,
+};
+
+enum testitem_bodygroups_to_hide_t
+{
+ TI_HIDEBG_HAT,
+ TI_HIDEBG_HEADPHONES,
+ TI_HIDEBG_MEDALS,
+ TI_HIDEBG_GRENADES,
+ TI_HIDEBG_BULLETS,
+ TI_HIDEBG_ARROWS,
+ TI_HIDEBG_RIGHTARM,
+ TI_HIDEBG_SHOES_SOCKS,
+
+ TI_HIDEBG_COUNT,
+};
+
+//-----------------------------------------------------------------------------
+// A dialog that handles adding or modifying an item we're testing
+//-----------------------------------------------------------------------------
+class CTestItemDialog : public vgui::EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CTestItemDialog, vgui::EditablePanel );
+public:
+ CTestItemDialog( vgui::Panel *parent, testitem_itemtypes_t iItemType, int iClassUsage, KeyValues *pExistingKVs );
+ ~CTestItemDialog( void );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ void Close( void );
+ void CloseAndUpdateItem( void );
+
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+ MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath );
+
+private:
+ void InitializeFromExistingKVs( KeyValues *pExistingKVs );
+ void SetEntryStep( testitem_entrysteps_t iStep );
+ void OpenSelectModelDialog( void );
+ void SetupItemComboBox( vgui::ComboBox *pComboBox );
+ void SetupPaintColorComboBox( void );
+ void SetupUnusualEffectComboBox( void );
+ void HandleClassCheckbuttonChecked( vgui::Panel *pPanel );
+
+private:
+ testitem_entrysteps_t m_iEntryStep;
+ testitem_itemtypes_t m_iItemType;
+ int m_iClassUsage;
+
+ vgui::FileOpenDialog *m_hImportModelDialog;
+ char m_szRelativePath[MAX_PATH];
+
+ CExLabel *m_pModelLabel;
+ CExLabel *m_pSelectModelLabel;
+ CExLabel *m_pNoItemsToReplaceLabel;
+ CExButton *m_pSelectModelButton;
+ CExButton *m_pOkButton;
+ vgui::ComboBox *m_pItemReplacedComboBox;
+ vgui::EditablePanel *m_pBodygroupPanel;
+ vgui::EditablePanel *m_pItemReplacedPanel;
+ vgui::CheckButton *m_pBodygroupCheckButtons[TI_HIDEBG_COUNT];
+
+ vgui::EditablePanel *m_pCustomizationsPanel;
+ vgui::ComboBox *m_pPaintColorComboBox;
+ vgui::ComboBox *m_pUnusualEffectComboBox;
+
+ vgui::EditablePanel *m_pExistingItemToTestPanel;
+ vgui::ComboBox *m_pExistingItemComboBox;
+};
+
+#endif // TESTITEM_DIALOG_H
diff --git a/game/client/tf/vgui/testitem_root.cpp b/game/client/tf/vgui/testitem_root.cpp
new file mode 100644
index 0000000..5769db7
--- /dev/null
+++ b/game/client/tf/vgui/testitem_root.cpp
@@ -0,0 +1,931 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/CheckButton.h"
+#include "testitem_root.h"
+#include "tf_controls.h"
+#include "c_playerresource.h"
+#include "gcsdk/gcmsg.h"
+#include "tf_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_gcmessages.h"
+#include "ienginevgui.h"
+#include "econ_item_system.h"
+#include "vgui_controls/FileOpenDialog.h"
+#include <filesystem.h>
+#include "ai_activity.h"
+#include "tf_gamerules.h"
+#include "vgui_controls/Slider.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+ConVar tf_testitem_recent( "tf_testitem_recent", "", FCVAR_ARCHIVE );
+
+KeyValues *g_pRootItemTestingKV = NULL;
+
+// Bot animations
+const char *g_pszBotAnimStrings[TI_BOTANIM_COUNT] =
+{
+ "#IT_BotAnim_Idle", // TI_BOTANIM_IDLE,
+ "#IT_BotAnim_Crouch_Idle", // TI_BOTANIM_CROUCH,
+ "#IT_BotAnim_Run", // TI_BOTANIM_RUN,
+ "#IT_BotAnim_Crouch_Walk", // TI_BOTANIM_CROUCH_WALK
+ "#IT_BotAnim_Jump", // TI_BOTANIM_JUMP
+};
+
+void UpdateItemTestKVs( void )
+{
+ KeyValues *pTmpCopy = g_pRootItemTestingKV->MakeCopy();
+ engine->ServerCmdKeyValues( pTmpCopy );
+
+ // Setup any clientside variables to match what we're sending to the server
+ TFGameRules()->ItemTesting_SetupFromKV( g_pRootItemTestingKV );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTestItemRoot::CTestItemRoot( vgui::Panel *parent ) : vgui::EditablePanel( parent, "TestItemRoot" )
+{
+ // Need to use the clientscheme (we're not parented to a clientscheme'd panel)
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_hEditItemDialog = NULL;
+ m_iClassUsage = 0;
+ m_pClassUsagePanel = NULL;
+ m_pTestingPanel = NULL;
+ m_hImportExportDialog = NULL;
+ m_bExporting = false;
+ memset( m_pItemTestButtons, 0, sizeof(m_pItemTestButtons) );
+ memset( m_pItemRemoveButtons, 0, sizeof(m_pItemRemoveButtons) );
+ memset( m_pClassCheckButtons, NULL, sizeof(m_pClassCheckButtons) );
+ memset( m_pItemTestKVs, 0, sizeof(m_pItemTestKVs) );
+
+ m_pBotAdditionPanel = new vgui::EditablePanel( this, "BotAdditionPanel" );
+ m_pBotSelectionComboBox = new vgui::ComboBox( m_pBotAdditionPanel, "BotSelectionComboBox", 9, false );
+ m_pBotSelectionComboBox->AddActionSignalTarget( this );
+ m_pAutoAddBotsCheckBox = new vgui::CheckButton( m_pBotAdditionPanel, "AutoAddBotsCheckBox", "" );
+ m_pAutoAddBotsCheckBox->AddActionSignalTarget( this );
+ m_pAutoAddBotsCheckBox->SetSelected( true );
+ m_pBotsOnBlueTeamCheckBox = new vgui::CheckButton( m_pBotAdditionPanel, "BotsOnBlueTeamCheckBox", "" );
+ m_pBotsOnBlueTeamCheckBox->AddActionSignalTarget( this );
+ m_pBotsOnBlueTeamCheckBox->SetSelected( true );
+ m_pAddBotButton = NULL;
+
+ m_pBotControlPanel = new CTestItemBotControls( this );
+ m_pBotControlPanel->SetEmbedded( true );
+
+ SetupComboBoxes();
+
+ if ( !g_pRootItemTestingKV )
+ {
+ g_pRootItemTestingKV = new KeyValues( "TestItems" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTestItemRoot::~CTestItemRoot( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::SetupComboBoxes( void )
+{
+ // Setup our Bot Selection combo box
+ KeyValues *pKeyValues;
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( iClass == TF_CLASS_CIVILIAN )
+ continue;
+ pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "class", iClass );
+ m_pBotSelectionComboBox->AddItem( g_aPlayerClassNames[iClass], pKeyValues );
+ }
+ m_pBotSelectionComboBox->SilentActivateItemByRow( 0 );
+
+ m_pBotControlPanel->SetupComboBoxes();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/TestItemRoot.res" );
+
+ m_pTestingPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName( "TestingPanel" ) );
+ if ( m_pTestingPanel )
+ {
+ for ( int i = 0; i < TI_TYPE_COUNT; i++ )
+ {
+ m_pItemTestButtons[i] = dynamic_cast<CExButton*>( m_pTestingPanel->FindChildByName( VarArgs("TestItemButton%d",i) ) );
+ m_pItemTestButtons[i]->AddActionSignalTarget( this );
+ m_pItemRemoveButtons[i] = dynamic_cast<CExButton*>( m_pTestingPanel->FindChildByName( VarArgs("RemoveItemButton%d",i) ) );
+ m_pItemRemoveButtons[i]->AddActionSignalTarget( this );
+ m_pItemTestLabels[i] = dynamic_cast<CExLabel*>( m_pTestingPanel->FindChildByName( VarArgs("TestItemEntry%d",i) ) );
+ }
+ }
+
+ m_pClassUsagePanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName( "ClassUsagePanel" ) );
+ if ( m_pClassUsagePanel )
+ {
+ for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ m_pClassCheckButtons[i] = dynamic_cast<vgui::CheckButton*>( m_pClassUsagePanel->FindChildByName( VarArgs("ClassCheckBox%d",i)) );
+ m_pClassCheckButtons[i]->AddActionSignalTarget( this );
+ }
+ }
+
+ m_pAddBotButton = dynamic_cast<CExButton*>( m_pBotAdditionPanel->FindChildByName( "AddBotButton" ) );
+ if ( m_pAddBotButton )
+ {
+ m_pAddBotButton->AddActionSignalTarget( this );
+ }
+ CExButton *pKickAllBotsButton = dynamic_cast<CExButton*>( m_pBotAdditionPanel->FindChildByName( "KickAllBotsButton" ) );
+ if ( pKickAllBotsButton )
+ {
+ pKickAllBotsButton->AddActionSignalTarget( this );
+ }
+
+ AddChildActionSignalTarget( this, "SteamWorkshopButtonSubButton", this, true );
+
+ UpdateTestItems();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::FireGameEvent( IGameEvent *event )
+{
+ const char *type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ Close();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::Close( void )
+{
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::OnSetTestItemKVs( KeyValues *pKV )
+{
+ if ( !pKV )
+ return;
+
+ testitem_itemtypes_t iItemType = (testitem_itemtypes_t)pKV->GetInt("item_type");
+ if ( iItemType <= TI_TYPE_UNKNOWN || iItemType > TI_TYPE_COUNT )
+ return;
+
+ // If we already have KVs for that slot, nuke them
+ if ( m_pItemTestKVs[iItemType] )
+ {
+ g_pRootItemTestingKV->RemoveSubKey( m_pItemTestKVs[iItemType] );
+ m_pItemTestKVs[iItemType]->deleteThis();
+ }
+
+ // Make our copy, and store it in the root KVs
+ m_pItemTestKVs[iItemType] = pKV->MakeCopy();
+ m_pItemTestKVs[iItemType]->SetName( VarArgs("Item%d",iItemType) );
+ g_pRootItemTestingKV->AddSubKey( m_pItemTestKVs[iItemType] );
+
+ UpdateTestItems();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::OnButtonChecked( KeyValues *pData )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( pData->GetPtr("panel") );
+
+ if ( pPanel == m_pAutoAddBotsCheckBox )
+ {
+ if ( m_pAutoAddBotsCheckBox->IsSelected() )
+ {
+ m_pAddBotButton->SetEnabled( false );
+ m_pBotSelectionComboBox->SetEnabled( false );
+ }
+ else
+ {
+ m_pAddBotButton->SetEnabled( true );
+ m_pBotSelectionComboBox->SetEnabled( true );
+ }
+
+ return;
+ }
+
+ // If they hit all classes, disable everything else.
+ if ( pPanel == m_pClassCheckButtons[0] )
+ {
+ bool bAllClass = m_pClassCheckButtons[0]->IsSelected();
+ for ( int i = 1; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ m_pClassCheckButtons[i]->SetEnabled( !bAllClass );
+ if ( bAllClass )
+ {
+ m_pClassCheckButtons[i]->SetSelected( false );
+ }
+ }
+ }
+ else
+ {
+ // If they've individually checked all boxes, switch to all-classes being checked
+ bool bAllChecked = true;
+ for ( int i = 1; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( !m_pClassCheckButtons[i]->IsSelected() )
+ {
+ bAllChecked = false;
+ break;
+ }
+ }
+
+ if ( bAllChecked )
+ {
+ m_pClassCheckButtons[0]->SetSelected( true );
+ }
+ }
+
+ m_iClassUsage = 0;
+ for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( m_pClassCheckButtons[i]->IsSelected() )
+ {
+ m_iClassUsage |= (1 << i);
+ }
+ }
+
+ UpdateTestItems();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::CommitSettingsToKV( void )
+{
+ g_pRootItemTestingKV->SetInt( "class_usage", m_iClassUsage );
+ g_pRootItemTestingKV->SetInt( "auto_add_bots", m_pAutoAddBotsCheckBox->IsSelected() );
+ g_pRootItemTestingKV->SetInt( "bots_on_blue_team", m_pBotsOnBlueTeamCheckBox->IsSelected() );
+
+ m_pBotControlPanel->CommitSettingsToKV();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::OnFileSelected(const char *fullpath)
+{
+ if ( m_bExporting )
+ {
+ ExportTestSetup( fullpath );
+ }
+ else
+ {
+ ImportTestSetup( fullpath );
+ }
+
+ // Nuke the file open dialog
+ m_hImportExportDialog->MarkForDeletion();
+ m_hImportExportDialog = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::ExportTestSetup( const char *pFilename )
+{
+ if ( !pFilename || !pFilename[0] )
+ return;
+
+ CommitSettingsToKV();
+
+ g_pRootItemTestingKV->SaveToFile( g_pFullFileSystem, pFilename );
+ tf_testitem_recent.SetValue( pFilename );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::ImportTestSetup( KeyValues *pKV )
+{
+ // Setup the class usage checkboxes
+ m_iClassUsage = pKV->GetInt( "class_usage", 0 );
+ for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ m_pClassCheckButtons[i]->SetSelected( (m_iClassUsage & (1<<i)) );
+ }
+
+ // Pull out the item KV blocks
+ for ( int i = 0; i < TI_TYPE_COUNT; i++ )
+ {
+ m_pItemTestKVs[i] = pKV->FindKey( VarArgs("Item%d",i) );
+ }
+
+ bool bAutoAdd = pKV->GetInt( "auto_add_bots", 1 );
+ m_pAutoAddBotsCheckBox->SetSelected(bAutoAdd);
+ bool bBlueTeamBots = pKV->GetInt( "bots_on_blue_team", 0 );
+ m_pBotsOnBlueTeamCheckBox->SetSelected(bBlueTeamBots);
+
+ m_pBotControlPanel->ImportTestSetup( pKV );
+
+ UpdateTestItems();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::ImportTestSetup( const char *pFilename )
+{
+ if ( !pFilename || !pFilename[0] )
+ return;
+
+ g_pRootItemTestingKV->deleteThis();
+ g_pRootItemTestingKV = new KeyValues( "TestItems" );
+ if ( g_pRootItemTestingKV->LoadFromFile( g_pFullFileSystem, pFilename ) )
+ {
+ ImportTestSetup( g_pRootItemTestingKV );
+ }
+ else
+ {
+ m_iClassUsage = 0;
+ memset( m_pItemTestKVs, 0, sizeof(m_pItemTestKVs) );
+
+ g_pRootItemTestingKV->deleteThis();
+ g_pRootItemTestingKV = new KeyValues( "TestItems" );
+
+ UpdateTestItems();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTestItemRoot::FindReplaceableItemsForSelectedClass( CUtlVector<item_definition_index_t> *pItemDefs, bool bWeapons )
+{
+ // Build our list of checked classes
+ bool bClasses[TF_LAST_NORMAL_CLASS];
+ for ( int i = 0; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ bClasses[i] = m_iClassUsage & (1 << i);
+ }
+
+ int iReplaceableItems = 0;
+
+ // Find all the weapons that can be used by the combination of classes we've checked
+ const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap();
+ FOR_EACH_MAP( mapItemDefs, i )
+ {
+ const CTFItemDefinition *pDef = dynamic_cast<const CTFItemDefinition *>( mapItemDefs[i] );
+
+ // Never show:
+ // - Hidden items
+ // - Items that don't have fixed qualities
+ if ( !pDef || pDef->IsHidden() || pDef->GetQuality() == k_unItemQuality_Any )
+ continue;
+
+ // Only show in staging (internal dev branch):
+ // - Normal quality items
+ // - Items that haven't asked to be shown in the armory
+ static const bool bIsStaging = ( engine->GetAppID() == 810 );
+ if ( !bIsStaging )
+ {
+ if ( pDef->GetQuality() == AE_NORMAL || !pDef->ShouldShowInArmory() )
+ continue;
+ }
+
+ // Make sure it's the right type of item
+ int iDefSlot = pDef->GetDefaultLoadoutSlot();
+ bool bValidSlot = false;
+ if ( bWeapons )
+ {
+ bValidSlot = (iDefSlot == LOADOUT_POSITION_PRIMARY || iDefSlot == LOADOUT_POSITION_SECONDARY || iDefSlot == LOADOUT_POSITION_MELEE );
+ if ( !bValidSlot )
+ {
+ bValidSlot = pDef->CanBePlacedInSlot(LOADOUT_POSITION_PRIMARY) || pDef->CanBePlacedInSlot(LOADOUT_POSITION_SECONDARY) || pDef->CanBePlacedInSlot(LOADOUT_POSITION_MELEE);
+ }
+ }
+ else
+ {
+ bValidSlot = (iDefSlot == LOADOUT_POSITION_HEAD || iDefSlot == LOADOUT_POSITION_MISC );
+ if ( !bValidSlot )
+ {
+ bValidSlot = pDef->CanBePlacedInSlot(LOADOUT_POSITION_HEAD) || pDef->CanBePlacedInSlot(LOADOUT_POSITION_MISC);
+ }
+ }
+ if ( !bValidSlot )
+ continue;
+
+ // Make sure it's used by all the checked classes
+ bool bUsable = false;
+ if ( bClasses[0] )
+ {
+ bUsable = pDef->CanBeUsedByAllClasses();
+ }
+ else
+ {
+ bUsable = true;
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( bClasses[iClass] && !pDef->CanBeUsedByClass(iClass) )
+ {
+ bUsable = false;
+ break;
+ }
+ }
+ }
+ if ( !bUsable )
+ continue;
+
+ if ( pItemDefs )
+ {
+ pItemDefs->AddToTail( pDef->GetDefinitionIndex() );
+ }
+ iReplaceableItems++;
+ }
+
+ return iReplaceableItems;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::UpdateTestItems( void )
+{
+ for ( int i = 0; i < TI_TYPE_COUNT; i++ )
+ {
+ // Weapon is handled specially, because it's tied to the class usage
+ if ( i == TI_TYPE_WEAPON )
+ {
+ int iValidWeapons = FindReplaceableItemsForSelectedClass( NULL, true );
+ m_pItemTestButtons[0]->SetEnabled( iValidWeapons );
+ if ( !iValidWeapons )
+ {
+ m_pItemTestLabels[0]->SetText( g_pVGuiLocalize->Find("#IT_ItemReplaced_Invalid") );
+ continue;
+ }
+ }
+
+ if ( m_pItemTestKVs[i] )
+ {
+ m_pItemTestButtons[i]->SetText( "#IT_Item_Edit" );
+
+ item_definition_index_t iExistingDef = m_pItemTestKVs[i]->GetInt( "existing_itemdef", INVALID_ITEM_DEF_INDEX );
+ if ( iExistingDef != INVALID_ITEM_DEF_INDEX )
+ {
+ CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinition(iExistingDef);
+ if ( pDef )
+ {
+ m_pItemTestLabels[i]->SetText( g_pVGuiLocalize->Find( pDef->GetItemBaseName() ) );
+ }
+ else
+ {
+ m_pItemTestLabels[i]->SetText( "#IT_TestingSlot_Empty" );
+ }
+ }
+ else
+ {
+ const char *pszModel = m_pItemTestKVs[i]->GetString("model_player", "#IT_TestingSlot_Empty");
+
+ char szModel[MAX_PATH+1]="";
+ Q_FileBase( pszModel, szModel, ARRAYSIZE( szModel ) );
+ m_pItemTestLabels[i]->SetText( szModel );
+ }
+ m_pItemRemoveButtons[i]->SetEnabled( true );
+ }
+ else
+ {
+ m_pItemTestButtons[i]->SetText( "#IT_Item_Add" );
+ m_pItemTestLabels[i]->SetText( "#IT_TestingSlot_Empty" );
+ m_pItemRemoveButtons[i]->SetEnabled( false );
+ }
+ }
+
+ // Hide the testing panel if we don't have any classes selected
+ if ( m_pTestingPanel )
+ {
+ m_pTestingPanel->SetVisible( m_iClassUsage != 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::CloseAndTestItem( void )
+{
+ // Go through and update the schema definitions before we send them off to the server
+ for ( int i = 0; i < TI_TYPE_COUNT; i++ )
+ {
+ if ( !m_pItemTestKVs[i] )
+ continue;
+
+ item_definition_index_t iNewDef = TESTITEM_DEFINITIONS_BEGIN_AT + i;
+ item_definition_index_t iItemDef = m_pItemTestKVs[i]->GetInt( "item_replace", INVALID_ITEM_DEF_INDEX );
+ ItemSystem()->GetItemSchema()->ItemTesting_CreateTestDefinition( iItemDef, iNewDef, m_pItemTestKVs[i] );
+ m_pItemTestKVs[i]->SetInt( "item_def", iNewDef );
+ }
+
+ // Not connected to a game?
+ if ( !TFGameRules() )
+ return;
+
+ CommitSettingsToKV();
+ g_pRootItemTestingKV->SetName("TestItems");
+ UpdateItemTestKVs();
+ Close();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemRoot::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "cancel" ) )
+ {
+ Close();
+ return;
+ }
+ else if ( !Q_stricmp( command, "ok" ) )
+ {
+ CloseAndTestItem();
+ return;
+ }
+ else if ( !Q_stricmp( command, "steamworkshop" ) )
+ {
+ Close();
+ engine->ClientCmd_Unrestricted( "OpenSteamWorkshopDialog;" );
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( false, true );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "item_test", 9 ) )
+ {
+ int iItemType = atoi( command+9 );
+ if ( iItemType >= 0 && iItemType < TI_TYPE_COUNT )
+ {
+ if (!m_hEditItemDialog.Get())
+ {
+ m_hEditItemDialog = vgui::SETUP_PANEL( new CTestItemDialog( this, (testitem_itemtypes_t)iItemType, m_iClassUsage, m_pItemTestKVs[iItemType] ) );
+ }
+ m_hEditItemDialog->InvalidateLayout( false, true );
+ m_hEditItemDialog->SetVisible( true );
+ m_hEditItemDialog->MoveToFront();
+ m_hEditItemDialog->SetKeyBoardInputEnabled(true);
+ m_hEditItemDialog->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( m_hEditItemDialog );
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "item_remove", 11 ) )
+ {
+ int iItemType = atoi( command+11 );
+ if ( iItemType >= 0 && iItemType < TI_TYPE_COUNT )
+ {
+ if ( m_pItemTestKVs[iItemType] )
+ {
+ g_pRootItemTestingKV->RemoveSubKey( m_pItemTestKVs[iItemType] );
+ m_pItemTestKVs[iItemType]->deleteThis();
+ m_pItemTestKVs[iItemType] = NULL;
+ }
+ UpdateTestItems();
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "export" ) || !Q_stricmp( command, "import" ) )
+ {
+ m_bExporting = ( command[0] == 'e' );
+
+ if (m_hImportExportDialog == NULL)
+ {
+ m_hImportExportDialog = new vgui::FileOpenDialog( NULL, "#ToolCustomizeTextureTitle", m_bExporting ? vgui::FOD_SAVE : vgui::FOD_OPEN, NULL );
+ m_hImportExportDialog->AddFilter( "*.itf", "#IT_TestingFiles", true );
+ m_hImportExportDialog->AddActionSignalTarget( this );
+
+ char szModelsDir[MAX_PATH];
+ m_hImportExportDialog->SetStartDirectory( g_pFullFileSystem->RelativePathToFullPath( "cfg", "MOD", szModelsDir, sizeof(szModelsDir) ) );
+ }
+ m_hImportExportDialog->DoModal( false );
+ m_hImportExportDialog->Activate();
+ return;
+ }
+ else if ( !Q_stricmp( command, "importrecent" ) )
+ {
+ ImportTestSetup( tf_testitem_recent.GetString() );
+ return;
+ }
+ else if ( !Q_stricmp( command, "bot_add" ) )
+ {
+ KeyValues *pKV = m_pBotSelectionComboBox->GetActiveItemUserData();
+ int iClass = pKV->GetInt( "class", TF_CLASS_UNDEFINED );
+ if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS )
+ {
+ bool bBlueTeam = m_pBotsOnBlueTeamCheckBox->IsSelected();
+ engine->ClientCmd_Unrestricted( VarArgs( "bot -team %s -class %s\n", bBlueTeam ? "blue" : "red", g_aPlayerClassNames_NonLocalized[iClass] ) );
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "bot_removeall" ) )
+ {
+ // Kick everyone above the first player
+ for ( int i = 2; i <= gpGlobals->maxClients; i++ )
+ {
+ C_BasePlayer *pPlayer = UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ engine->ClientCmd_Unrestricted( VarArgs( "kickid %d\n", pPlayer->GetUserID() ) );
+ }
+ }
+ return;
+ }
+
+
+ BaseClass::OnCommand( command );
+}
+
+
+static vgui::DHANDLE<CTestItemRoot> g_hTestItemRoot;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void OpenTestItemRoot( void )
+{
+ if (!g_hTestItemRoot.Get())
+ {
+ g_hTestItemRoot = vgui::SETUP_PANEL( new CTestItemRoot( NULL ) );
+ }
+
+ g_hTestItemRoot->SetVisible( true );
+ g_hTestItemRoot->MakePopup();
+ g_hTestItemRoot->MoveToFront();
+ g_hTestItemRoot->SetKeyBoardInputEnabled(true);
+ g_hTestItemRoot->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_hTestItemRoot );
+
+ g_hTestItemRoot->MakeReadyForUse();
+ if ( g_pRootItemTestingKV )
+ {
+ g_hTestItemRoot->ImportTestSetup( g_pRootItemTestingKV );
+ }
+}
+ConCommand testitem( "itemtest", OpenTestItemRoot, "Open the item testing panel.", FCVAR_NONE );
+
+
+
+//========================================================================================================================================
+// BOT CONTROLS PANEL
+//========================================================================================================================================
+static vgui::DHANDLE<CTestItemBotControls> g_hTestItemBotControls;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTestItemBotControls::CTestItemBotControls( vgui::Panel *parent ) : vgui::EditablePanel( parent, "TestItemBotControls" )
+{
+ // Need to use the clientscheme (we're not parented to a clientscheme'd panel)
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_pBotAnimationComboBox = new vgui::ComboBox( this, "BotAnimationComboBox", 9, false );
+ m_pBotAnimationComboBox->AddActionSignalTarget( this );
+ m_pBotForceFireCheckBox = new vgui::CheckButton( this, "BotForceFireCheckBox", "" );
+ m_pBotForceFireCheckBox->AddActionSignalTarget( this );
+ m_pBotTurntableCheckBox = new vgui::CheckButton( this, "BotTurntableCheckBox", "" );
+ m_pBotTurntableCheckBox->AddActionSignalTarget( this );
+ m_pBotViewScanCheckBox = new vgui::CheckButton( this, "BotViewScanCheckBox", "" );
+ m_pBotViewScanCheckBox->AddActionSignalTarget( this );
+ m_pBotAnimationSpeedSlider = new vgui::Slider( this, "BotAnimationSpeedSlider" );
+ m_pBotAnimationSpeedSlider->SetRange( 0, 100 );
+ m_pBotAnimationSpeedSlider->SetNumTicks( 10 );
+ m_pBotAnimationSpeedSlider->AddActionSignalTarget( this );
+
+ m_bEmbedded = false;
+
+ SetupComboBoxes();
+
+ if ( !g_pRootItemTestingKV )
+ {
+ g_pRootItemTestingKV = new KeyValues( "TestItems" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTestItemBotControls::~CTestItemBotControls( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::SetupComboBoxes( void )
+{
+ KeyValues *pKeyValues;
+ // Setup our bot animation combo box
+ for ( int i = 0; i < TI_BOTANIM_COUNT; i++ )
+ {
+ pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "anim", i );
+ m_pBotAnimationComboBox->AddItem( g_pszBotAnimStrings[i], pKeyValues );
+ }
+ m_pBotAnimationComboBox->SilentActivateItemByRow( 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::FireGameEvent( IGameEvent *event )
+{
+ const char *type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ Close();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::Close( void )
+{
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::ImportTestSetup( KeyValues *pKV )
+{
+ bool bForceFire = pKV->GetInt( "bot_force_fire", 0 );
+ m_pBotForceFireCheckBox->SetSelected(bForceFire);
+ bool bViewScan = pKV->GetInt( "bot_view_scan", 0 );
+ m_pBotViewScanCheckBox->SetSelected(bViewScan);
+ bool bTurnTable = pKV->GetInt( "bot_turntable", 0 );
+ m_pBotTurntableCheckBox->SetSelected(bTurnTable);
+
+ int iAnim = g_pRootItemTestingKV->GetInt( "bot_anim", TI_BOTANIM_IDLE );
+ m_pBotAnimationComboBox->SilentActivateItemByRow( iAnim );
+
+ int iAnimSpeed = g_pRootItemTestingKV->GetInt( "bot_animspeed", 100 );
+ m_pBotAnimationSpeedSlider->SetValue( iAnimSpeed, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/TestItemBotControls.res" );
+
+ // Dumb, but the slider needs to have its scheme forcibly loaded to make it create the left/right text
+ m_pBotAnimationSpeedSlider->InvalidateLayout( true, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName( "OkButton" ) );
+ if ( pButton )
+ {
+ pButton->SetVisible( !m_bEmbedded );
+ }
+ pButton = dynamic_cast<CExButton*>( FindChildByName( "CloseButton" ) );
+ if ( pButton )
+ {
+ pButton->SetVisible( !m_bEmbedded );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "cancel" ) )
+ {
+ Close();
+ return;
+ }
+ else if ( !Q_stricmp( command, "ok" ) )
+ {
+ UpdateBots();
+ return;
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( false, true );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::UpdateBots( void )
+{
+ // Not connected to a game?
+ if ( !TFGameRules() )
+ return;
+
+ CommitSettingsToKV();
+
+ g_pRootItemTestingKV->SetName("TestItemsBotUpdate");
+ UpdateItemTestKVs();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTestItemBotControls::CommitSettingsToKV( void )
+{
+ g_pRootItemTestingKV->SetInt( "bot_force_fire", m_pBotForceFireCheckBox->IsSelected() );
+ g_pRootItemTestingKV->SetInt( "bot_view_scan", m_pBotViewScanCheckBox->IsSelected() );
+ g_pRootItemTestingKV->SetInt( "bot_turntable", m_pBotTurntableCheckBox->IsSelected() );
+
+ KeyValues *pKV = m_pBotAnimationComboBox->GetActiveItemUserData();
+ int iAnim = pKV->GetInt( "anim", TI_BOTANIM_IDLE );
+ g_pRootItemTestingKV->SetInt( "bot_anim", iAnim );
+
+ int iAnimSpeed = clamp( m_pBotAnimationSpeedSlider->GetValue(), 0, 100 );
+ g_pRootItemTestingKV->SetInt( "bot_animspeed", iAnimSpeed );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void OpenTestItemBotControls( void )
+{
+ if (!g_hTestItemBotControls.Get())
+ {
+ g_hTestItemBotControls = vgui::SETUP_PANEL( new CTestItemBotControls( NULL ) );
+ }
+
+ g_hTestItemBotControls->SetVisible( true );
+ g_hTestItemBotControls->MakePopup();
+ g_hTestItemBotControls->MoveToFront();
+ g_hTestItemBotControls->SetKeyBoardInputEnabled(true);
+ g_hTestItemBotControls->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_hTestItemBotControls );
+
+ g_hTestItemBotControls->MakeReadyForUse();
+ if ( g_pRootItemTestingKV )
+ {
+ g_hTestItemBotControls->ImportTestSetup( g_pRootItemTestingKV );
+ g_hTestItemBotControls->SetEmbedded( false );
+ }
+}
+ConCommand testitem_botcontrols( "itemtest_botcontrols", OpenTestItemBotControls, "Open the item testing bot control panel.", FCVAR_NONE );
diff --git a/game/client/tf/vgui/testitem_root.h b/game/client/tf/vgui/testitem_root.h
new file mode 100644
index 0000000..1fed8b3
--- /dev/null
+++ b/game/client/tf/vgui/testitem_root.h
@@ -0,0 +1,109 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TESTITEM_ROOT_H
+#define TESTITEM_ROOT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "tf_controls.h"
+#include "testitem_dialog.h"
+
+
+//-----------------------------------------------------------------------------
+// A panel that handles the overall item testing process
+//-----------------------------------------------------------------------------
+class CTestItemBotControls : public vgui::EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CTestItemBotControls, vgui::EditablePanel );
+public:
+ CTestItemBotControls( vgui::Panel *parent );
+ ~CTestItemBotControls( void );
+
+ void SetupComboBoxes( void );
+ virtual void FireGameEvent( IGameEvent *event );
+ void ImportTestSetup( KeyValues *pKV );
+ void Close( void );
+ void SetEmbedded( bool bEmbedded ) { m_bEmbedded = bEmbedded; InvalidateLayout(); }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ void UpdateBots( void );
+ void CommitSettingsToKV( void );
+
+private:
+ vgui::ComboBox *m_pBotAnimationComboBox;
+ vgui::Slider *m_pBotAnimationSpeedSlider;
+ vgui::CheckButton *m_pBotForceFireCheckBox;
+ vgui::CheckButton *m_pBotTurntableCheckBox;
+ vgui::CheckButton *m_pBotViewScanCheckBox;
+ bool m_bEmbedded;
+};
+
+
+//-----------------------------------------------------------------------------
+// A panel that handles the overall item testing process
+//-----------------------------------------------------------------------------
+class CTestItemRoot : public vgui::EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CTestItemRoot, vgui::EditablePanel );
+public:
+ CTestItemRoot( vgui::Panel *parent );
+ ~CTestItemRoot( void );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ void Close( void );
+ void CloseAndTestItem( void );
+ void UpdateTestItems( void );
+ int FindReplaceableItemsForSelectedClass( CUtlVector<item_definition_index_t> *pItemDefs = NULL, bool bWeapons = false );
+ void ExportTestSetup( const char *pFilename );
+ void ImportTestSetup( const char *pFilename );
+ void ImportTestSetup( KeyValues *pKV );
+ void CommitSettingsToKV( void );
+
+ MESSAGE_FUNC_PARAMS( OnSetTestItemKVs, "SetTestItemKVs", pKV );
+ MESSAGE_FUNC_PARAMS( OnButtonChecked, "CheckButtonChecked", pData );
+ MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath );
+
+private:
+ void SetupComboBoxes( void );
+
+private:
+ int m_iClassUsage;
+
+ vgui::EditablePanel *m_pClassUsagePanel;
+ vgui::EditablePanel *m_pTestingPanel;
+ vgui::EditablePanel *m_pBotAdditionPanel;
+ CTestItemBotControls *m_pBotControlPanel;
+
+ // Testing panel
+ CExButton *m_pItemTestButtons[TI_TYPE_COUNT];
+ CExButton *m_pItemRemoveButtons[TI_TYPE_COUNT];
+ CExLabel *m_pItemTestLabels[TI_TYPE_COUNT];
+ vgui::CheckButton *m_pClassCheckButtons[TF_LAST_NORMAL_CLASS];
+ KeyValues *m_pItemTestKVs[TI_TYPE_COUNT];
+
+ // Bot addition panel
+ vgui::ComboBox *m_pBotSelectionComboBox;
+ vgui::CheckButton *m_pAutoAddBotsCheckBox;
+ vgui::CheckButton *m_pBotsOnBlueTeamCheckBox;
+ CExButton *m_pAddBotButton;
+
+ vgui::DHANDLE<CTestItemDialog> m_hEditItemDialog;
+ vgui::FileOpenDialog *m_hImportExportDialog;
+ bool m_bExporting;
+};
+
+#endif // TESTITEM_ROOT_H
diff --git a/game/client/tf/vgui/tf_arenateammenu.cpp b/game/client/tf/vgui/tf_arenateammenu.cpp
new file mode 100644
index 0000000..c5ff4c3
--- /dev/null
+++ b/game/client/tf/vgui/tf_arenateammenu.cpp
@@ -0,0 +1,426 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#include "cbase.h"
+
+#include <vgui_controls/Label.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui_controls/RichText.h>
+#include <vgui_controls/Frame.h>
+#include <vgui/IScheme.h>
+#include <game/client/iviewport.h>
+#include <vgui/IVGui.h>
+#include <KeyValues.h>
+#include <filesystem.h>
+
+#include "vguicenterprint.h"
+#include "tf_controls.h"
+#include "basemodelpanel.h"
+#include "tf_arenateammenu.h"
+#include <convar.h>
+#include "IGameUIFuncs.h" // for key bindings
+#include "hud.h" // for gEngfuncs
+#include "c_tf_player.h"
+#include "tf_gamerules.h"
+#include "c_team.h"
+#include "tf_hud_notification_panel.h"
+#include "inputsystem/iinputsystem.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFArenaTeamMenu::CTFArenaTeamMenu( IViewPort *pViewPort ) : CTeamMenu( pViewPort )
+{
+ SetMinimizeButtonVisible( false );
+ SetMaximizeButtonVisible( false );
+ SetCloseButtonVisible( false );
+ SetVisible( false );
+ SetKeyBoardInputEnabled( true );
+
+ m_iTeamMenuKey = BUTTON_CODE_INVALID;
+
+ m_pAutoTeamButton = new CTFTeamButton( this, "teambutton2" );
+ m_pSpecTeamButton = new CTFTeamButton( this, "teambutton3" );
+ m_pSpecLabel = new CExLabel( this, "TeamMenuSpectate", "" );
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+#else
+ m_pCancelButton = new CExButton( this, "CancelButton", "#TF_Cancel" );
+ m_pJoinAutoHintIcon = m_pJoinSpectatorsHintIcon = m_pCancelHintIcon = nullptr;
+#endif
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ m_bRedDisabled = false;
+ m_bBlueDisabled = false;
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/HudArenaTeamMenu_SC.res" );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/HudArenaTeamMenu.res" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CTFArenaTeamMenu::~CTFArenaTeamMenu()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/HudArenaTeamMenu_SC.res" );
+
+ m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) );
+ m_pJoinAutoHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinAutoHintIcon" ) );
+ m_pJoinSpectatorsHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinSpectatorsHintIcon" ) );
+
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/HudArenaTeamMenu.res" );
+ SetMouseInputEnabled( true );
+
+ m_pCancelHintIcon = m_pJoinAutoHintIcon = m_pJoinSpectatorsHintIcon = nullptr;
+ }
+
+ Update();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::ShowPanel( bool bShow )
+{
+ if ( BaseClass::IsVisible() == bShow )
+ return;
+
+ if ( !gameuifuncs || !gViewPortInterface || !engine )
+ return;
+
+ if ( bShow )
+ {
+ if ( !C_TFPlayer::GetLocalTFPlayer() )
+ return;
+
+ if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN &&
+ C_TFPlayer::GetLocalTFPlayer() &&
+ C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam()
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED )
+ {
+ SetVisible( false );
+
+ CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
+ if ( pNotifyPanel )
+ {
+ pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeTeamNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() );
+ }
+
+ return;
+ }
+
+ gViewPortInterface->ShowPanel( PANEL_CLASS_RED, false );
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, false );
+
+ engine->CheckPoint( "TeamMenu" );
+
+ InvalidateLayout( true, true );
+
+ Activate();
+
+ // get key bindings if shown
+ m_iTeamMenuKey = gameuifuncs->GetButtonCodeForBind( "changeteam" );
+ m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" );
+
+ GetFocusNavGroup().SetCurrentFocus( m_pAutoTeamButton->GetVPanel(), m_pAutoTeamButton->GetVPanel() );
+ ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 );
+ }
+ else
+ {
+ SetVisible( false );
+
+ if ( IsConsole() )
+ {
+ // Close the door behind us
+ CTFArenaTeamMenu *pButton = dynamic_cast< CTFArenaTeamMenu *> ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorExited();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate the right selection hint icon, depending on the focus group number selected
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::ActivateSelectIconHint( int focus_group_number )
+{
+ if ( m_pJoinAutoHintIcon ) m_pJoinAutoHintIcon->SetVisible( false );
+ if ( m_pJoinSpectatorsHintIcon ) m_pJoinSpectatorsHintIcon->SetVisible( false );
+
+ CSCHintIcon* icon = nullptr;
+ switch ( focus_group_number )
+ {
+ case 1: icon = m_pJoinAutoHintIcon; break;
+ case 2: icon = m_pJoinSpectatorsHintIcon; break;
+ }
+
+ if ( icon )
+ {
+ icon->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called to update the menu with new information
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::Update( void )
+{
+ BaseClass::Update();
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) )
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", true );
+ }
+#else
+ if ( m_pCancelButton )
+ {
+ m_pCancelButton->SetVisible( true );
+ if ( m_pCancelHintIcon )
+ {
+ m_pCancelHintIcon->SetVisible( true );
+ }
+ }
+#endif
+ }
+ else
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", false );
+ }
+#else
+ if ( m_pCancelButton && m_pCancelButton->IsVisible() )
+ {
+ m_pCancelButton->SetVisible( false );
+ if ( m_pCancelHintIcon )
+ {
+ m_pCancelHintIcon->SetVisible( false );
+ }
+ }
+#endif
+ }
+}
+
+#ifdef _X360
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::Join_Team( const CCommand &args )
+{
+ if ( args.ArgC() > 1 )
+ {
+ char cmd[256];
+ Q_snprintf( cmd, sizeof( cmd ), "jointeam_nomenus %s", args.Arg( 1 ) );
+ OnCommand( cmd );
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: chooses and loads the text page to display that describes mapName map
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::LoadMapPage( const char *mapName )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::OnKeyCodePressed( KeyCode code )
+{
+ if ( ( m_iTeamMenuKey != BUTTON_CODE_INVALID && m_iTeamMenuKey == code ) ||
+ code == KEY_XBUTTON_BACK ||
+ code == KEY_XBUTTON_B ||
+ code == STEAMCONTROLLER_B )
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) )
+ {
+ ShowPanel( false );
+ }
+ }
+ else if( code == KEY_SPACE || code == STEAMCONTROLLER_Y )
+ {
+ engine->ClientCmd( "jointeam auto" );
+
+ ShowPanel( false );
+ OnClose();
+ }
+ else if( code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A )
+ {
+ // select the active focus
+ if ( GetFocusNavGroup().GetCurrentFocus() )
+ {
+ ipanel()->SendMessage( GetFocusNavGroup().GetCurrentFocus()->GetVPanel(), new KeyValues( "PressButton" ), GetVPanel() );
+ }
+ }
+ else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT )
+ {
+ CTFTeamButton *pButton;
+
+ pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorExited();
+ GetFocusNavGroup().RequestFocusNext( pButton->GetVPanel() );
+ }
+ else
+ {
+ GetFocusNavGroup().RequestFocusNext( NULL );
+ }
+
+ pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorEntered();
+ }
+
+ ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 );
+ }
+ else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT )
+ {
+ CTFTeamButton *pButton;
+
+ pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorExited();
+ GetFocusNavGroup().RequestFocusPrev( pButton->GetVPanel() );
+ }
+ else
+ {
+ GetFocusNavGroup().RequestFocusPrev( NULL );
+ }
+
+ pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorEntered();
+ }
+
+ ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 );
+ }
+ else if ( m_iScoreBoardKey != BUTTON_CODE_INVALID && m_iScoreBoardKey == code )
+ {
+ gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, true );
+ gViewPortInterface->PostMessageToPanel( PANEL_SCOREBOARD, new KeyValues( "PollHideCode", "code", code ) );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when the user picks a team
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::OnCommand( const char *command )
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( Q_stricmp( command, "vguicancel" ) )
+ {
+ // we're selecting a team, so make sure it's not the team we're already on before sending to the server
+ if ( pLocalPlayer && ( Q_strstr( command, "jointeam " ) ) )
+ {
+ engine->ClientCmd( command );
+ }
+ else if ( pLocalPlayer && ( Q_strstr( command, "jointeam_nomenus " ) ) )
+ {
+ engine->ClientCmd( command );
+ }
+ }
+
+ BaseClass::OnCommand( command );
+ ShowPanel( false );
+ OnClose();
+}
+
+//-----------------------------------------------------------------------------
+// Frame-based update
+//-----------------------------------------------------------------------------
+void CTFArenaTeamMenu::OnTick()
+{
+ // update the number of players on each team
+
+ // enable or disable buttons based on team limit
+
+ C_Team *pRed = GetGlobalTeam( TF_TEAM_RED );
+ C_Team *pBlue = GetGlobalTeam( TF_TEAM_BLUE );
+
+ if ( !pRed || !pBlue )
+ return;
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( !pLocalPlayer )
+ return;
+
+ CTFGameRules *pRules = TFGameRules();
+
+ if ( !pRules )
+ return;
+
+ if ( m_pSpecTeamButton && m_pSpecLabel )
+ {
+ {
+ if ( mp_allowspectators.GetBool() )
+ {
+ if ( !m_pSpecTeamButton->IsVisible() )
+ {
+ m_pSpecTeamButton->SetVisible( true );
+ m_pSpecLabel->SetVisible( true );
+ }
+ }
+ else
+ {
+ if ( m_pSpecTeamButton->IsVisible() )
+ {
+ m_pSpecTeamButton->SetVisible( false );
+ m_pSpecLabel->SetVisible( false );
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_arenateammenu.h b/game/client/tf/vgui/tf_arenateammenu.h
new file mode 100644
index 0000000..daab38e
--- /dev/null
+++ b/game/client/tf/vgui/tf_arenateammenu.h
@@ -0,0 +1,79 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#ifndef TF_ARENATEAMMENU_H
+#define TF_ARENATEAMMENU_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_controls.h"
+#include <teammenu.h>
+#include "tf_teammenu.h"
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Displays the team menu
+//-----------------------------------------------------------------------------
+class CTFArenaTeamMenu : public CTeamMenu
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFArenaTeamMenu, CTeamMenu );
+
+public:
+ CTFArenaTeamMenu( IViewPort *pViewPort );
+ ~CTFArenaTeamMenu();
+
+ void Update();
+ void ShowPanel( bool bShow );
+
+#ifdef _X360
+ CON_COMMAND_MEMBER_F( CTFTeamMenu, "join_team", Join_Team, "Send a jointeam command", 0 );
+#endif
+
+protected:
+ virtual void ApplySchemeSettings(vgui::IScheme *pScheme);
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+
+ // command callbacks
+ virtual void OnCommand( const char *command );
+
+ virtual void LoadMapPage( const char *mapName );
+
+ virtual void OnTick( void );
+
+ virtual const char *GetName( void ) { return PANEL_ARENA_TEAM; }
+
+private:
+
+ CTFTeamButton *m_pAutoTeamButton;
+ CTFTeamButton *m_pSpecTeamButton;
+ CExLabel *m_pSpecLabel;
+
+#ifdef _X360
+ CTFFooter *m_pFooter;
+#else
+ CExButton *m_pCancelButton;
+#endif
+
+ CSCHintIcon *m_pCancelHintIcon;
+ CSCHintIcon *m_pJoinAutoHintIcon;
+ CSCHintIcon *m_pJoinSpectatorsHintIcon;
+
+ bool m_bRedDisabled;
+ bool m_bBlueDisabled;
+
+
+private:
+ enum { NUM_TEAMS = 3 };
+
+ ButtonCode_t m_iTeamMenuKey;
+
+ void ActivateSelectIconHint( int focus_group_number );
+};
+
+#endif // TF_ARENATEAMMENU_H
diff --git a/game/client/tf/vgui/tf_asyncpanel.cpp b/game/client/tf/vgui/tf_asyncpanel.cpp
new file mode 100644
index 0000000..dd63119
--- /dev/null
+++ b/game/client/tf/vgui/tf_asyncpanel.cpp
@@ -0,0 +1,121 @@
+#include "cbase.h"
+#include "tf_asyncpanel.h"
+#include "econ_controls.h"
+
+static const float k_flRequestInterval = 5.f;
+static const float k_flNeverRequestUpdate = -1.f;
+
+
+CBaseASyncPanel::CBaseASyncPanel( Panel *pParent, const char *pszPanelName )
+ : EditablePanel( pParent, pszPanelName )
+ , m_flLastRequestTime( 0.f )
+ , m_flLastUpdatedTime( 0.f )
+ , m_bDataInitialized( false )
+ , m_bSettingsApplied( false )
+{
+ ivgui()->AddTickSignal( GetVPanel(), 1.f );
+}
+
+void CBaseASyncPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_bSettingsApplied = true;
+ m_flLastUpdatedTime = 0.f; // Force a refresh
+}
+
+
+void CBaseASyncPanel::LoadControlSettings(const char *dialogResourceName, const char *pathID, KeyValues *pPreloadedKeyValues, KeyValues *pConditions )
+{
+ m_vecLoadingPanels.Purge();
+ m_vecPanelsToShow.Purge();
+
+ BaseClass::LoadControlSettings( dialogResourceName, pathID, pPreloadedKeyValues, pConditions );
+}
+
+void CBaseASyncPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ FOR_EACH_VEC( m_vecLoadingPanels, i )
+ {
+ m_vecLoadingPanels[ i ]->SetVisible( true );
+ }
+
+ FOR_EACH_VEC( m_vecPanelsToShow, i )
+ {
+ m_vecPanelsToShow[ i ]->SetVisible( false );
+ }
+}
+
+
+void CBaseASyncPanel::OnChildSettingsApplied( KeyValues *pInResourceData, Panel *pChild )
+{
+ const char *pszAsync = pInResourceData->GetString( "asynchandling", NULL );
+
+ if ( pszAsync == NULL )
+ return;
+
+ if ( FStrEq( pszAsync, "content" ) )
+ {
+ m_vecPanelsToShow[ m_vecPanelsToShow.AddToTail() ].Set( pChild );
+ }
+ else if ( FStrEq( pszAsync, "loading" ) )
+ {
+ m_vecLoadingPanels[ m_vecLoadingPanels.AddToTail() ].Set( pChild );
+ }
+}
+
+bool CBaseASyncPanel::IsInitialized() const
+{
+ return m_bDataInitialized;
+}
+
+void CBaseASyncPanel::PresentDataIfReady()
+{
+ if ( m_bDataInitialized && m_bSettingsApplied )
+ {
+ FOR_EACH_VEC( m_vecLoadingPanels, i )
+ {
+ m_vecLoadingPanels[ i ]->SetVisible( false );
+ }
+
+ FOR_EACH_VEC( m_vecPanelsToShow, i )
+ {
+ m_vecPanelsToShow[ i ]->SetVisible( true );
+ }
+
+ // stop ticking. job is done.
+ ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks if data is ready. If so, mark the time and hide the loading image
+//-----------------------------------------------------------------------------
+void CBaseASyncPanel::CheckForData()
+{
+ m_flLastRequestTime = Plat_FloatTime();
+ if ( CheckForData_Internal() )
+ {
+ m_flLastUpdatedTime = Plat_FloatTime();
+ m_bDataInitialized = true;
+ PresentDataIfReady();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check if we need to check for data
+//-----------------------------------------------------------------------------
+void CBaseASyncPanel::OnTick()
+{
+ const float flTimeSinceUpdate = Plat_FloatTime() - m_flLastUpdatedTime;
+ const float flTimeSinceRequest = Plat_FloatTime() - m_flLastRequestTime;
+ // Need an update if we're beyodn the refresh delay, and our refresh delay isn't k_flNeverRequestUpdate, or if we're just not initialized
+ const bool bNeedsUpdate = ( ( flTimeSinceUpdate > m_flRefreshDelay ) && ( m_flRefreshDelay != k_flNeverRequestUpdate ) ) || m_flLastUpdatedTime == 0.f;
+
+ if ( m_bSettingsApplied && bNeedsUpdate && flTimeSinceRequest > k_flRequestInterval )
+ {
+ CheckForData();
+ }
+}
diff --git a/game/client/tf/vgui/tf_asyncpanel.h b/game/client/tf/vgui/tf_asyncpanel.h
new file mode 100644
index 0000000..478c83f
--- /dev/null
+++ b/game/client/tf/vgui/tf_asyncpanel.h
@@ -0,0 +1,40 @@
+#ifndef TF_ASYNCPANEL
+#define TF_ASYNCPANEL
+
+#include "vgui_controls/EditablePanel.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CBaseASyncPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseASyncPanel, EditablePanel );
+public:
+ CBaseASyncPanel( Panel *pParent, const char *pszPanelName );
+ virtual ~CBaseASyncPanel() {}
+
+ bool IsInitialized() const;
+ void CheckForData();
+ virtual void OnTick() OVERRIDE;
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void LoadControlSettings(const char *dialogResourceName, const char *pathID = NULL, KeyValues *pPreloadedKeyValues = NULL, KeyValues *pConditions = NULL) OVERRIDE;
+protected:
+ virtual void OnChildSettingsApplied( KeyValues *pInResourceData, Panel *pChild ) OVERRIDE;
+
+private:
+ void PresentDataIfReady();
+ virtual bool CheckForData_Internal() = 0;
+
+ bool m_bDataInitialized;
+ bool m_bSettingsApplied;
+ float m_flLastRequestTime;
+ float m_flLastUpdatedTime;
+ CUtlVector< PHandle > m_vecLoadingPanels;
+ CUtlVector< PHandle > m_vecPanelsToShow;
+ CPanelAnimationVar( float, m_flRefreshDelay, "refresh_delay", "-1.f" );
+};
+
+#endif //TF_ASYNCPANEL \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_badge_panel.cpp b/game/client/tf/vgui/tf_badge_panel.cpp
new file mode 100644
index 0000000..d66918f
--- /dev/null
+++ b/game/client/tf/vgui/tf_badge_panel.cpp
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "modelimagepanel.h"
+#include "tf_badge_panel.h"
+
+DECLARE_BUILD_FACTORY( CTFBadgePanel );
+
+CTFBadgePanel::CTFBadgePanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName )
+{
+ m_pBadgePanel = new CModelImagePanel( this, "BadgePanel" );
+ m_nPrevLevel = 0;
+}
+
+
+void CTFBadgePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pBadgePanel->LoadControlSettings( "resource/ui/BadgePanel.res" );
+}
+
+
+void CTFBadgePanel::SetupBadge( const IProgressionDesc* pProgress, const LevelInfo_t& levelInfo )
+{
+ if ( !pProgress )
+ return;
+
+ pProgress->SetupBadgePanel( m_pBadgePanel, levelInfo );
+
+ if ( m_nPrevLevel != levelInfo.m_nLevelNum )
+ {
+ m_nPrevLevel = levelInfo.m_nLevelNum;
+ m_pBadgePanel->InvalidateImage();
+ }
+}
+
+
+void CTFBadgePanel::SetupBadge( const IProgressionDesc* pProgress, const CSteamID& steamID )
+{
+ if ( pProgress && steamID.IsValid() )
+ {
+ SetupBadge( pProgress, pProgress->YieldingGetLevelForSteamID( steamID ) );
+ }
+}
diff --git a/game/client/tf/vgui/tf_badge_panel.h b/game/client/tf/vgui/tf_badge_panel.h
new file mode 100644
index 0000000..edcba8f
--- /dev/null
+++ b/game/client/tf/vgui/tf_badge_panel.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#ifndef TF_BADGE_PANEL_H
+#define TF_BADGE_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "tf_match_description.h"
+
+class CTFBadgePanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFBadgePanel, vgui::EditablePanel );
+public:
+ CTFBadgePanel( vgui::Panel *pParent, const char *pName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ void SetupBadge( const IProgressionDesc* pProgress, const LevelInfo_t& levelInfo );
+ void SetupBadge( const IProgressionDesc* pProgress, const CSteamID& steamID );
+
+private:
+ class CModelImagePanel *m_pBadgePanel;
+ uint32 m_nPrevLevel;
+};
+
+
+#endif // TF_BADGE_PANEL_H
diff --git a/game/client/tf/vgui/tf_classmenu.cpp b/game/client/tf/vgui/tf_classmenu.cpp
new file mode 100644
index 0000000..c6ba14c
--- /dev/null
+++ b/game/client/tf/vgui/tf_classmenu.cpp
@@ -0,0 +1,1703 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_classmenu.h"
+#include "IGameUIFuncs.h" // for key bindings
+#include "tf_hud_notification_panel.h"
+#include "character_info_panel.h"
+#include "playerspawncache.h"
+#include "iclientmode.h"
+#include "econ_gcmessages.h"
+#include "gc_clientsystem.h"
+#include "vgui/IInput.h"
+#include <vgui_controls/PanelListPanel.h>
+#include <vgui_controls/ScrollBarSlider.h>
+#include "tf_gamerules.h"
+#include "engine/IEngineSound.h"
+#include "inputsystem/iinputsystem.h"
+
+#if defined( REPLAY_ENABLED )
+#include "replay/iclientreplaycontext.h"
+#include "replay/ireplaysystem.h"
+#include "replay/ienginereplay.h"
+#include "replay/shared_defs.h"
+#endif
+
+extern IGameUIFuncs *gameuifuncs; // for key binding details
+
+using namespace vgui;
+
+ConVar _cl_classmenuopen( "_cl_classmenuopen", "0", 0, "internal cvar used to tell server when class menu is open" );
+
+#if defined( REPLAY_ENABLED )
+ConVar replay_replaywelcomedlgcount( "replay_replaywelcomedlgcount", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "The number of times the replay help dialog has displayed." );
+#endif
+
+ConVar tf_mvm_classupgradehelpcount( "tf_mvm_classupgradehelpcount", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "The number of times the player upgrade help dialog has displayed." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFClassTipsItemPanel::CTFClassTipsItemPanel( Panel *parent, const char *name, int iListItemID ) : BaseClass( parent, name )
+{
+ m_pTipIcon = new vgui::ImagePanel( this, "TipIcon" );
+ m_pTipLabel = new CExLabel( this, "TipLabel", "" );
+
+// m_pTipIcon = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "TipIcon" ) );
+// m_pTipLabel = dynamic_cast<CExLabel*>( FindChildByName( "TipLabel" ) );
+}
+
+CTFClassTipsItemPanel::~CTFClassTipsItemPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassTipsItemPanel::SetClassTip( const wchar_t *pwszText, const char *pszIcon )
+{
+ // Set tf_english string and .res icon path
+ if ( m_pTipLabel && m_pTipIcon )
+ {
+ if ( pszIcon )
+ {
+ m_pTipIcon->SetImage( pszIcon );
+ }
+ else
+ {
+ m_pTipIcon->SetVisible( false );
+// int iX, iY = 0;
+// m_pTipLabel->GetPos( iX, iY );
+// int nIconWidth = m_pTipIcon->GetWide();
+// m_pTipLabel->SetPos( ( iX - nIconWidth ), iY );
+ }
+ m_pTipLabel->SetText( pwszText );
+ }
+
+ InvalidateLayout( true, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassTipsItemPanel::ApplySchemeSettings( IScheme* pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/ClassTipsItem.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFClassTipsListPanel : public PanelListPanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClassTipsListPanel, PanelListPanel );
+
+public:
+ CTFClassTipsListPanel( Panel *parent, const char *panelName )
+ : PanelListPanel( parent, panelName )
+ {
+ m_pScrollBar = GetScrollbar();
+
+ m_pUpArrow = new CExImageButton( this, "UpArrow", "" );
+ if ( m_pUpArrow )
+ {
+ m_pUpArrow->AddActionSignalTarget( m_pScrollBar );
+ m_pUpArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 0));
+ m_pUpArrow->GetImage()->SetShouldScaleImage( true );
+ m_pUpArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ m_pUpArrow->SetAlpha( 255 );
+ m_pUpArrow->SetPaintBackgroundEnabled( false );
+ m_pUpArrow->SetVisible( false );
+ m_pUpArrow->PassMouseTicksTo( m_pScrollBar );
+ m_pUpArrow->SetImageDefault( "chalkboard_scroll_up" );
+ }
+
+ m_pDownArrow = new CExImageButton( this, "DownArrow", "" );
+ if ( m_pDownArrow )
+ {
+ m_pDownArrow->AddActionSignalTarget( m_pScrollBar );
+ m_pDownArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 1));
+ m_pDownArrow->GetImage()->SetShouldScaleImage( true );
+ m_pDownArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ m_pDownArrow->SetAlpha( 255 );
+ m_pDownArrow->SetPaintBackgroundEnabled( false );
+ m_pDownArrow->SetVisible( false );
+ m_pDownArrow->PassMouseTicksTo( m_pScrollBar );
+ m_pDownArrow->SetImageDefault( "chalkboard_scroll_down" );
+ }
+
+ if ( m_pScrollBar )
+ {
+ m_pScrollBar->SetOverriddenButtons( m_pUpArrow, m_pDownArrow );
+ m_pScrollBar->SetVisible( false );
+ }
+
+ m_pLine = new vgui::ImagePanel( this, "Line" );
+ m_pBox = new vgui::ImagePanel( this, "Box" );
+ if ( m_pLine )
+ {
+ m_pLine->SetImage( "chalkboard_scroll_line" );
+ m_pLine->SetShouldScaleImage( true );
+ }
+
+ if ( m_pBox )
+ {
+ m_pBox->SetImage( "chalkboard_scroll_box" );
+ m_pBox->SetShouldScaleImage( true );
+ }
+
+ SetScrollBarImagesVisible( false );
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ ~CTFClassTipsListPanel()
+ {
+ ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+private:
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ int GetListHeight( void )
+ {
+ int nHeight = 0;
+
+ for ( int i = FirstItem(); i < GetItemCount(); i++ )
+ {
+ vgui::Panel *pClassTipsItemPanel = GetItemPanel( i );
+ if ( pClassTipsItemPanel )
+ {
+ nHeight += pClassTipsItemPanel->GetTall();
+ }
+ }
+
+ return nHeight;
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void SetScrollBarImagesVisible( bool visible )
+ {
+ if ( m_pDownArrow && m_pDownArrow->IsVisible() != visible )
+ {
+ m_pDownArrow->SetVisible( visible );
+ m_pDownArrow->SetEnabled( visible );
+ }
+
+ if ( m_pUpArrow && m_pUpArrow->IsVisible() != visible )
+ {
+ m_pUpArrow->SetVisible( visible );
+ m_pUpArrow->SetEnabled( visible );
+ }
+
+ if ( m_pLine && m_pLine->IsVisible() != visible )
+ {
+ m_pLine->SetVisible( visible );
+ }
+
+ if ( m_pBox && m_pBox->IsVisible() != visible )
+ {
+ m_pBox->SetVisible( visible );
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void OnTick()
+ {
+ if ( !IsVisible() )
+ return;
+
+ if ( m_pDownArrow && m_pUpArrow && m_pLine && m_pBox )
+ {
+ if ( m_pScrollBar && m_pScrollBar->IsVisible() && GetListHeight() > GetTall() )
+ {
+ SetScrollBarImagesVisible ( true );
+
+ // set the alpha on the up arrow
+ int nMin, nMax;
+ m_pScrollBar->GetRange( nMin, nMax );
+ int nScrollPos = m_pScrollBar->GetValue();
+ int nRangeWindow = m_pScrollBar->GetRangeWindow();
+ int nBottom = nMax - nRangeWindow;
+ if ( nBottom < 0 )
+ {
+ nBottom = 0;
+ }
+
+ int nAlpha = ( nScrollPos - nMin <= 0 ) ? 90 : 255;
+ m_pUpArrow->SetAlpha( nAlpha );
+
+ // set the alpha on the down arrow
+ nAlpha = ( nScrollPos >= nBottom ) ? 90 : 255;
+ m_pDownArrow->SetAlpha( nAlpha );
+
+ ScrollBarSlider *pSlider = m_pScrollBar->GetSlider();
+ if ( pSlider && pSlider->GetRangeWindow() > 0 )
+ {
+ int x, y, w, t, min, max;
+ m_pLine->GetBounds( x, y, w, t );
+ pSlider->GetNobPos( min, max );
+ m_pBox->SetBounds( x, y + min, w, ( max - min ) );
+ }
+ }
+ else
+ {
+ // turn off our images
+ SetScrollBarImagesVisible ( false );
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( m_pScrollBar )
+ {
+ m_pScrollBar->SetZPos( 500 );
+ m_pUpArrow->SetZPos( 501 );
+ m_pDownArrow->SetZPos( 501 );
+
+ // turn off painting the vertical scrollbar
+ m_pScrollBar->SetPaintBackgroundEnabled( false );
+ m_pScrollBar->SetPaintBorderEnabled( false );
+ m_pScrollBar->SetPaintEnabled( false );
+ m_pScrollBar->SetScrollbarButtonsVisible( false );
+ m_pScrollBar->GetButton(0)->SetMouseInputEnabled( false );
+ m_pScrollBar->GetButton(1)->SetMouseInputEnabled( false );
+
+ if ( m_pScrollBar->IsVisible() )
+ {
+ int nMin, nMax;
+ m_pScrollBar->GetRange( nMin, nMax );
+ m_pScrollBar->SetValue( nMin );
+
+ int nScrollbarWide = m_pScrollBar->GetWide();
+
+ int wide, tall;
+ GetSize( wide, tall );
+
+ if ( m_pUpArrow )
+ {
+ m_pUpArrow->SetBounds( wide - nScrollbarWide, 0, nScrollbarWide, nScrollbarWide );
+ m_pUpArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
+ }
+
+ if ( m_pLine )
+ {
+ m_pLine->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, tall - ( 2 * nScrollbarWide ) );
+ }
+
+ if ( m_pBox )
+ {
+ m_pBox->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, nScrollbarWide );
+ }
+
+ if ( m_pDownArrow )
+ {
+ m_pDownArrow->SetBounds( wide - nScrollbarWide, tall - nScrollbarWide, nScrollbarWide, nScrollbarWide );
+ m_pDownArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
+ }
+
+ SetScrollBarImagesVisible( false );
+ }
+ }
+ }
+
+ vgui::ScrollBar *m_pScrollBar;
+ CExImageButton *m_pUpArrow;
+ CExImageButton *m_pDownArrow;
+ vgui::ImagePanel *m_pLine;
+ vgui::ImagePanel *m_pBox;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFClassTipsPanel : public EditablePanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClassTipsPanel, EditablePanel );
+
+public:
+ CTFClassTipsPanel( Panel *parent, const char *panelName )
+ : EditablePanel( parent, panelName )
+ {
+ m_pClassTipsListPanel = new CTFClassTipsListPanel( this, "ClassTipsListPanel" );
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ ~CTFClassTipsPanel()
+ {
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ void SetClass( int iClass )
+ {
+ const char *pPathID = IsX360() ? "MOD" : "GAME";
+ m_fmtResFilename.sprintf( "classes/%s.res", g_aRawPlayerClassNames[ iClass ] );
+ if ( !g_pFullFileSystem->FileExists( m_fmtResFilename.Access(), pPathID ) &&
+ g_pFullFileSystem->FileExists( "classes/default.res", pPathID ) )
+ {
+ m_fmtResFilename.sprintf( "classes/default.res" );
+ }
+
+ if ( m_pClassTipsListPanel )
+ {
+ m_pClassTipsListPanel->DeleteAllItems();
+
+ int nScrollToItem = 0;
+
+ // Get tip count
+ const wchar_t *wzTipCount = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_Count", iClass ) );
+ int nTipCount = wzTipCount ? _wtoi( wzTipCount ) : 0;
+ for ( int iTip = 1; iTip < nTipCount+1; ++iTip )
+ {
+ const wchar_t *pwszText = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d", iClass, iTip ) );
+ const wchar_t *pwszTextMvM = g_pVGuiLocalize->Find( CFmtStr( "#ClassTips_%d_%d_MvM", iClass, iTip ) );
+ wchar_t *pwszIcon = g_pVGuiLocalize->Find( CFmtStr( "ClassTips_%d_%d_Icon", iClass, iTip ) );
+ char szIcon[MAX_PATH];
+
+ szIcon[0] = 0;
+ if ( pwszIcon )
+ {
+ g_pVGuiLocalize->ConvertUnicodeToANSI( pwszIcon, szIcon, sizeof( szIcon ) );
+ }
+
+ // Don't load MvM tips outside the mode
+ if ( pwszTextMvM )
+ {
+ if ( !TFGameRules()->IsMannVsMachineMode() )
+ continue;
+
+ // If we're MvM mode, remember first MvM tip
+ if ( !nScrollToItem )
+ nScrollToItem = iTip;
+ }
+
+ // Create a TipsItemPanel for each tip
+ if ( pwszText || pwszTextMvM )
+ {
+ CTFClassTipsItemPanel *pClassTipsItemPanel = new CTFClassTipsItemPanel( this, "ClassTipsItemPanel", iTip );
+ if ( pwszText )
+ {
+ pClassTipsItemPanel->SetClassTip( pwszText, szIcon );
+ }
+ else if ( pwszTextMvM )
+ {
+ pClassTipsItemPanel->SetClassTip( pwszTextMvM, szIcon );
+ }
+
+ m_pClassTipsListPanel->AddItem( NULL, pClassTipsItemPanel );
+ }
+ }
+
+ if ( m_pClassTipsListPanel->GetItemCount() > 0 )
+ {
+ m_pClassTipsListPanel->SetFirstColumnWidth( 0 );
+ m_pClassTipsListPanel->ScrollToItem( nScrollToItem );
+ }
+ }
+
+ InvalidateLayout( true, true );
+ }
+
+private:
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ //-----------------------------------------------------------------------------
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/ClassTipsList.res" );
+ }
+
+ CTFClassTipsListPanel *m_pClassTipsListPanel;
+
+ CFmtStr m_fmtResFilename;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFClassMenu::CTFClassMenu( IViewPort *pViewPort )
+: CClassMenu( pViewPort )
+{
+ MakePopup();
+
+ m_mouseoverButtons.RemoveAll();
+
+ m_iClassMenuKey = BUTTON_CODE_INVALID;
+ m_iCurrentClassIndex = TF_CLASS_HEAVYWEAPONS;
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+#endif
+
+ m_pTFPlayerModelPanel = NULL;
+ m_pSelectAClassLabel = NULL;
+
+ m_pClassTipsPanel = new CTFClassTipsPanel( this, "ClassTipsPanel" );
+
+ Q_memset( m_pClassButtons, 0, sizeof( m_pClassButtons ) );
+
+#ifndef _X360
+ char tempName[MAX_PATH];
+ for ( int i = 0 ; i < CLASS_COUNT_IMAGES ; ++i )
+ {
+ Q_snprintf( tempName, sizeof( tempName ), "countImage%d", i );
+ m_ClassCountImages[i] = new CTFImagePanel( this, tempName );
+ }
+
+ m_pCountLabel = NULL;
+
+ m_pLocalPlayerImage = new CTFImagePanel( this, "localPlayerImage" );
+ m_pLocalPlayerBG = new CTFImagePanel( this, "localPlayerBG" );
+ m_iLocalPlayerClass = TEAM_UNASSIGNED;
+
+ m_pClassButtons[TF_CLASS_SCOUT] = new CExImageButton( this, "scout", "", this );
+ m_pClassButtons[TF_CLASS_SOLDIER] = new CExImageButton( this, "soldier", "", this );
+ m_pClassButtons[TF_CLASS_PYRO] = new CExImageButton( this, "pyro", "", this );
+ m_pClassButtons[TF_CLASS_DEMOMAN] = new CExImageButton( this, "demoman", "", this );
+ m_pClassButtons[TF_CLASS_MEDIC] = new CExImageButton( this, "medic", "", this );
+ m_pClassButtons[TF_CLASS_HEAVYWEAPONS] = new CExImageButton( this, "heavyweapons", "", this );
+ m_pClassButtons[TF_CLASS_SNIPER] = new CExImageButton( this, "sniper", "", this );
+ m_pClassButtons[TF_CLASS_ENGINEER] = new CExImageButton( this, "engineer", "", this );
+ m_pClassButtons[TF_CLASS_SPY] = new CExImageButton( this, "spy", "", this );
+ m_pClassButtons[TF_CLASS_RANDOM] = new CExImageButton( this, "random", "", this );
+#endif
+
+ m_pEditLoadoutButton = NULL;
+ m_nBaseMusicGuid = -1;
+
+ ListenForGameEvent( "localplayer_changeteam" );
+ ListenForGameEvent( "show_match_summary" );
+
+ Q_memset( m_pMvmUpgradeImages, 0, sizeof( m_pMvmUpgradeImages ) );
+ m_pMvmUpgradeImages[TF_CLASS_SCOUT] = new vgui::ImagePanel( this, "MvMUpgradeImageScout" );
+ m_pMvmUpgradeImages[TF_CLASS_SOLDIER] = new vgui::ImagePanel( this, "MvMUpgradeImageSolider" );
+ m_pMvmUpgradeImages[TF_CLASS_PYRO] = new vgui::ImagePanel( this, "MvMUpgradeImagePyro" );
+ m_pMvmUpgradeImages[TF_CLASS_DEMOMAN] = new vgui::ImagePanel( this, "MvMUpgradeImageDemoman" );
+ m_pMvmUpgradeImages[TF_CLASS_MEDIC] = new vgui::ImagePanel( this, "MvMUpgradeImageMedic" );
+ m_pMvmUpgradeImages[TF_CLASS_HEAVYWEAPONS] = new vgui::ImagePanel( this, "MvMUpgradeImageHeavy" );
+ m_pMvmUpgradeImages[TF_CLASS_SNIPER] = new vgui::ImagePanel( this, "MvMUpgradeImageSniper" );
+ m_pMvmUpgradeImages[TF_CLASS_ENGINEER] = new vgui::ImagePanel( this, "MvMUpgradeImageEngineer" );
+ m_pMvmUpgradeImages[TF_CLASS_SPY] = new vgui::ImagePanel( this, "MvMUpgradeImageSpy" );
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ m_pClassHintIcons[i] = nullptr;
+ }
+
+ // Load the .res file
+ if ( ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/ClassSelection_SC.res" );
+ m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) );
+ m_pEditLoadoutHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "EditLoadoutHintIcon" ) );
+
+ m_pClassHintIcons[TF_CLASS_SCOUT] = dynamic_cast< CSCHintIcon* >( FindChildByName( "ScoutHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_SOLDIER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SoldierHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_PYRO] = dynamic_cast< CSCHintIcon* >( FindChildByName( "PyroHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_DEMOMAN] = dynamic_cast< CSCHintIcon* >( FindChildByName( "DemomanHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_HEAVYWEAPONS] = dynamic_cast< CSCHintIcon* >( FindChildByName( "HeavyHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_MEDIC] = dynamic_cast< CSCHintIcon* >( FindChildByName( "MedicHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_SPY] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SpyHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_ENGINEER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "EngineerHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_SNIPER] = dynamic_cast< CSCHintIcon* >( FindChildByName( "SniperHintIcon" ) );
+ m_pClassHintIcons[TF_CLASS_RANDOM] = dynamic_cast< CSCHintIcon* >( FindChildByName( "RandomHintIcon" ) );
+
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ if ( m_pClassHintIcons[i] )
+ {
+ m_pClassHintIcons[i]->SetVisible( false );
+ }
+ }
+
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/ClassSelection.res" );
+ m_pCancelHintIcon = m_pEditLoadoutHintIcon = nullptr;
+ SetMouseInputEnabled( true );
+ }
+
+ m_pTFPlayerModelPanel = dynamic_cast<CTFPlayerModelPanel*>( FindChildByName("TFPlayerModel") );
+ m_pSelectAClassLabel = dynamic_cast<CExLabel*>( FindChildByName( "ClassMenuSelect" ) );
+ m_pEditLoadoutButton = dynamic_cast<CExButton*>( FindChildByName( "EditLoadoutButton" ) );
+
+ const char *pTeamExtension = GetTeamNumber() == TF_TEAM_BLUE ? "blu" : "red";
+ for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
+ {
+ if ( !m_pClassButtons[i] )
+ continue;
+
+ if ( !IsValidTFPlayerClass( i ) && i != TF_CLASS_RANDOM )
+ continue;
+
+ m_pClassButtons[i]->SetImageSelected( CFmtStr( "class_sel_sm_%s_%s", g_aRawPlayerClassNamesShort[i], pTeamExtension ).Access() );
+ if( i != TF_CLASS_RANDOM )
+ {
+ m_pClassButtons[i]->SetArmedSound("misc/null.wav");
+ }
+ }
+}
+
+void CTFClassMenu::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+#ifndef _X360
+ m_pCountLabel = dynamic_cast< CExLabel * >( FindChildByName( "CountLabel" ) );
+
+ if ( m_pCountLabel )
+ {
+ m_pCountLabel->SizeToContents();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFClassMenu::GetCurrentPlayerClass()
+{
+ int iClass = TF_CLASS_HEAVYWEAPONS;
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED )
+ {
+ iClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ }
+
+ return iClass;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExImageButton *CTFClassMenu::GetCurrentClassButton()
+{
+ const int iClass = GetCurrentPlayerClass();
+ m_iCurrentClassIndex = iRemapIndexToClass[ iClass ];
+ return m_pClassButtons[iClass];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::ShowPanel( bool bShow )
+{
+ if ( bShow )
+ {
+ // Hide the other class menu
+ if ( gViewPortInterface )
+ {
+ gViewPortInterface->ShowPanel( GetTeamNumber() == TF_TEAM_BLUE ? PANEL_CLASS_RED : PANEL_CLASS_BLUE, false );
+ }
+
+ // can't change class if you're on the losing team during the "bonus time" after a team has won the round
+ if ( ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN &&
+ C_TFPlayer::GetLocalTFPlayer() &&
+ C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam()
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED
+ && GetSpectatorMode() == OBS_MODE_NONE ) ||
+ TFGameRules()->State_Get() == GR_STATE_GAME_OVER ||
+ ( TFGameRules()->IsInTraining() && C_TFPlayer::GetLocalTFPlayer() &&
+ ( C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass() == NULL || C_TFPlayer::GetLocalTFPlayer()->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) )
+ {
+ SetVisible( false );
+
+ CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
+ if ( pNotifyPanel )
+ {
+ if ( C_TFPlayer::GetLocalTFPlayer() )
+ {
+ pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeClassNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() );
+ }
+ }
+
+ return;
+ }
+
+ engine->CheckPoint( "ClassMenu" );
+
+ // Force us to reload our scheme, in case Steam Controller stuff has changed.
+ InvalidateLayout( true, true );
+
+ Activate();
+
+ m_iClassMenuKey = gameuifuncs->GetButtonCodeForBind( "changeclass" );
+ m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" );
+
+ SelectClass( GetCurrentPlayerClass() );
+ }
+ else
+ {
+ SetVisible( false );
+ if ( m_pTFPlayerModelPanel )
+ {
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+ }
+ }
+}
+
+const char *g_pszLegacyClassSelectVCDWeapons[TF_LAST_NORMAL_CLASS] =
+{
+ "", // TF_CLASS_UNDEFINED = 0,
+ "", // TF_CLASS_SCOUT, // weapons handled individually
+ "", // TF_CLASS_SNIPER, // weapons handled individually
+ "", // TF_CLASS_SOLDIER, // weapons handled individually
+ "tf_weapon_grenadelauncher", // TF_CLASS_DEMOMAN,
+ "tf_weapon_medigun", // TF_CLASS_MEDIC,
+ "tf_weapon_minigun", // TF_CLASS_HEAVYWEAPONS,
+ "tf_weapon_flamethrower", // TF_CLASS_PYRO,
+ "", // TF_CLASS_SPY, // weapons handled individually
+ "tf_weapon_wrench", // TF_CLASS_ENGINEER,
+};
+
+int g_iLegacyClassSelectWeaponSlots[TF_LAST_NORMAL_CLASS] =
+{
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_UNDEFINED = 0,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_SNIPER,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_SOLDIER,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_DEMOMAN,
+ LOADOUT_POSITION_SECONDARY, // TF_CLASS_MEDIC,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_HEAVYWEAPONS,
+ LOADOUT_POSITION_PRIMARY, // TF_CLASS_PYRO,
+ LOADOUT_POSITION_MELEE, // TF_CLASS_SPY,
+ LOADOUT_POSITION_MELEE, // TF_CLASS_ENGINEER,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::UpdateButtonSelectionStates( int iClass )
+{
+ // Set the correct button as selected, all other buttons as not selected
+ for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
+ {
+ if ( !m_pClassButtons[ i ] )
+ continue;
+
+ m_pClassButtons[ i ]->SetSelected( i == iClass );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::SelectClass( int iClass )
+{
+ if ( !engine->IsInGame() )
+ return;
+
+ if ( !m_pTFPlayerModelPanel )
+ return;
+
+ if ( !m_pClassTipsPanel )
+ return;
+
+ if ( !m_pEditLoadoutButton )
+ return;
+
+ // Update select hint icon for Steam Controller
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ if ( m_pClassHintIcons[i] )
+ {
+ m_pClassHintIcons[i]->SetVisible( i == iClass );
+ }
+ }
+
+ // Were we random? If so, we'll force our class to refresh later to prevent the
+ // model panel thinking the class hasn't changed.
+ const bool bClassWasRandom = m_iCurrentClassIndex == TF_CLASS_RANDOM;
+
+ // Cache current player class
+ m_iCurrentClassIndex = iClass;
+
+ UpdateButtonSelectionStates( iClass );
+
+ bool bRandomClass = iClass == TF_CLASS_RANDOM;
+ if ( !IsValidTFPlayerClass( iClass ) && !bRandomClass )
+ {
+ m_pTFPlayerModelPanel->SetVisible( false );
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+ return;
+ }
+
+ m_pTFPlayerModelPanel->SetVisible( true );
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+
+ if ( bRandomClass )
+ {
+ m_pEditLoadoutButton->SetVisible( false );
+ if ( m_pEditLoadoutHintIcon )
+ {
+ m_pEditLoadoutHintIcon->SetVisible( false );
+ }
+
+ MDLHandle_t hModel = mdlcache->FindMDL( "models/class_menu/random_class_icon.mdl" );
+ m_pTFPlayerModelPanel->SetMDL( hModel );
+ m_pTFPlayerModelPanel->SetSequence( ACT_IDLE );
+ m_pTFPlayerModelPanel->InvalidateLayout( true, true ); // Updates position
+ m_pTFPlayerModelPanel->SetSkin( GetTeamNumber() == TF_TEAM_RED ? 0 : 1 );
+ mdlcache->Release( hModel ); // counterbalance addref from within FindMDL
+ }
+ else
+ {
+ bool bIsRobot = false;
+ // Check for Robot
+ static CSchemaAttributeDefHandle pAttrDef_PlayerRobot( "appear as mvm robot" );
+ for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
+ if ( !pItemData )
+ continue;
+ if ( FindAttribute( pItemData, pAttrDef_PlayerRobot ) )
+ {
+ bIsRobot = true;
+ break;
+ }
+ }
+
+ /*if ( pLocalPlayer )
+ {
+ int iRobot = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iRobot, appear_as_mvm_robot );
+ bIsRobot = iRobot ? true : false;
+ }*/
+ m_pTFPlayerModelPanel->SetToPlayerClass( iClass, bIsRobot, bClassWasRandom );
+
+ m_pEditLoadoutButton->SetVisible( true );
+ if ( m_pEditLoadoutHintIcon )
+ {
+ m_pEditLoadoutHintIcon->SetVisible( true );
+ }
+
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ int iTeam = GetTeamNumber();
+ m_pTFPlayerModelPanel->SetTeam( iTeam );
+ }
+
+ LoadItems();
+ }
+
+ m_pClassTipsPanel->SetClass( iClass );
+
+ enginesound->StopSoundByGuid( m_nBaseMusicGuid );
+ CBroadcastRecipientFilter filter;
+ char nClassMusicStr[64];
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ sprintf( nClassMusicStr, "music.mvm_class_menu_0%i", iClass );
+ }
+ else
+ {
+ sprintf( nClassMusicStr, "music.class_menu_0%i", iClass );
+ }
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, nClassMusicStr );
+ m_nBaseMusicGuid = enginesound->GetGuidForLastSoundEmitted();
+
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::LoadItems()
+{
+ const int iClass = m_pTFPlayerModelPanel->GetPlayerClass();
+
+ m_pTFPlayerModelPanel->ClearCarriedItems();
+
+ static CSchemaAttributeDefHandle pAttrDef_DisableFancyLoadoutAnim( "disable fancy class select anim" );
+ bool bCanUseFancyClassSelectAnimation = true;
+
+ static CSchemaAttributeDefHandle pAttrDef_ClassSelectOverrideVCD( "class select override vcd" );
+ CAttribute_String attrClassSelectOverrideVCD;
+
+ const char *pszVCD = "class_select";
+
+ for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
+ if ( pItemData && pItemData->IsValid() )
+ {
+ m_pTFPlayerModelPanel->AddCarriedItem( pItemData );
+
+ // Certain items have different shapes and would interfere with our class select animations.
+ bCanUseFancyClassSelectAnimation = bCanUseFancyClassSelectAnimation
+ && !pItemData->FindAttribute( pAttrDef_DisableFancyLoadoutAnim );
+
+ // Some items want to override the class select VCD
+ if ( pItemData->FindAttribute( pAttrDef_ClassSelectOverrideVCD, &attrClassSelectOverrideVCD ) )
+ {
+ const char *pszClassSelectOverrideVCD = attrClassSelectOverrideVCD.value().c_str();
+ if ( pszClassSelectOverrideVCD && *pszClassSelectOverrideVCD )
+ {
+ pszVCD = pszClassSelectOverrideVCD;
+ }
+ }
+ }
+ }
+
+ m_pTFPlayerModelPanel->PlayVCD( bCanUseFancyClassSelectAnimation ? pszVCD : NULL, g_pszLegacyClassSelectVCDWeapons[iClass] );
+ m_pTFPlayerModelPanel->HoldItemInSlot( g_iLegacyClassSelectWeaponSlots[iClass] );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnKeyCodePressed( KeyCode code )
+{
+ m_KeyRepeat.KeyDown( code );
+
+ if ( code > KEY_0 && code <= KEY_9 )
+ {
+ const int iButton = code - KEY_0;
+ const int iClass = iRemapIndexToClass[ iButton ];
+ SelectClass( iClass );
+ Go();
+ }
+ else if ( code > KEY_PAD_0 && code <= KEY_PAD_9 )
+ {
+ const int iButton = code - KEY_PAD_0;
+ const int iClass = iRemapIndexToClass[ iButton ];
+ SelectClass( iClass );
+ Go();
+ }
+ else if( code == KEY_ENTER || code == KEY_SPACE || code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A )
+ {
+ Go();
+ }
+ else if ( ( m_iClassMenuKey != BUTTON_CODE_INVALID && m_iClassMenuKey == code ) ||
+ code == KEY_XBUTTON_BACK ||
+ code == KEY_XBUTTON_B ||
+ code == STEAMCONTROLLER_B ||
+ code == KEY_0 ||
+ code == KEY_PAD_0 )
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && ( pLocalPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) )
+ {
+ ShowPanel( false );
+ }
+ }
+ else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT )
+ {
+ int loopCheck = 0;
+ int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex );
+
+ do
+ {
+ loopCheck++;
+ nCurrentClass++;
+ nCurrentClass = ( nCurrentClass % TF_CLASS_MENU_BUTTONS );
+ } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) );
+
+ SelectClass( iRemapIndexToClass[ nCurrentClass ] );
+ }
+ else if ( code == STEAMCONTROLLER_Y )
+ {
+ OnCommand( "openloadout" );
+ }
+ else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT )
+ {
+ int loopCheck = 0;
+ int nCurrentClass = GetRemappedMenuIndexForClass( m_iCurrentClassIndex );
+
+ do
+ {
+ loopCheck++;
+ nCurrentClass--;
+ if( nCurrentClass <= 0 )
+ {
+ nCurrentClass = GetRemappedMenuIndexForClass( TF_CLASS_RANDOM );
+ }
+ } while( ( m_pClassButtons[ iRemapIndexToClass[nCurrentClass] ] == NULL ) && ( loopCheck < TF_CLASS_MENU_BUTTONS ) );
+
+ SelectClass( iRemapIndexToClass[ nCurrentClass ] );
+ }
+ else if( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == STEAMCONTROLLER_DPAD_UP )
+ {
+ // Scroll class info text up
+ if ( g_lastPanel )
+ {
+ CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) );
+
+ if ( pRichText )
+ {
+ PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", 1) );
+ }
+ }
+ }
+ else if( code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == STEAMCONTROLLER_DPAD_DOWN )
+ {
+ // Scroll class info text up
+ if ( g_lastPanel )
+ {
+ CExRichText *pRichText = dynamic_cast< CExRichText * >( g_lastPanel->FindChildByName( "classInfo" ) );
+
+ if ( pRichText )
+ {
+ PostMessage( pRichText, new KeyValues("MoveScrollBarDirect", "delta", -1) );
+ }
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnKeyCodeReleased( vgui::KeyCode code )
+{
+ m_KeyRepeat.KeyUp( code );
+
+ BaseClass::OnKeyCodeReleased( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnThink()
+{
+ vgui::KeyCode code = m_KeyRepeat.KeyRepeated();
+ if ( code )
+ {
+ OnKeyCodePressed( code );
+ }
+
+ // Get mouse cursor position
+ int aCursorPos[2];
+ vgui::input()->GetCursorPos( aCursorPos[0], aCursorPos[1] );
+
+ // Go through all buttons - if the mouse is within one, select that class
+ for ( int i = 0; i < ARRAYSIZE( m_pClassButtons ); ++i )
+ {
+ if ( !m_pClassButtons[ i ] )
+ continue;
+
+ if ( m_iCurrentClassIndex != i && m_pClassButtons[ i ]->IsWithin( aCursorPos[0], aCursorPos[1] ) )
+ {
+ SelectClass( i );
+ }
+ }
+
+ //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
+ }
+
+ BaseClass::OnThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::SetCancelButtonVisible( bool bVisible )
+{
+ SetVisibleButton( "CancelButton", bVisible );
+ if ( m_pCancelHintIcon )
+ {
+ m_pCancelHintIcon->SetVisible( bVisible );
+ }
+
+ if ( m_pSelectAClassLabel )
+ {
+ m_pSelectAClassLabel->SetVisible( !bVisible );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::Update()
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ // Force them to pick a class if they haven't picked one yet.
+ if ( ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() != TF_CLASS_UNDEFINED ) )
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", true );
+ }
+#else
+ SetCancelButtonVisible( true );
+
+ if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() )
+ {
+ SetVisibleButton( "ResetButton", true );
+ }
+ else
+ {
+ SetVisibleButton( "ResetButton", false );
+ }
+#endif
+ }
+ else
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", false );
+ }
+#else
+ SetCancelButtonVisible( false );
+ SetVisibleButton( "ResetButton", false );
+#endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Panel *CTFClassMenu::CreateControlByName( const char *controlName )
+{
+ if ( !Q_stricmp( "CIconPanel", controlName ) )
+ {
+ return new CIconPanel( this, "icon_panel" );
+ }
+ else
+ {
+ return BaseClass::CreateControlByName( controlName );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Catch the mouseover event and set the active class
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnShowPage( vgui::Panel *panel, const char *pagename )
+{
+ for ( int i = 0; i < TF_CLASS_MENU_BUTTONS; i++ )
+ {
+ if (m_pClassButtons[i] == panel )
+ {
+// SelectClass( i );
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Draw nothing
+//-----------------------------------------------------------------------------
+void CTFClassMenu::PaintBackground( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Do things that should be done often, eg number of players in the
+// selected class
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnTick( void )
+{
+ //When a player changes teams, their class and team values don't get here
+ //necessarily before the command to update the class menu. This leads to the cancel button
+ //being visible and people cancelling before they have a class. check for class == TF_CLASS_UNDEFINED and if so
+ //hide the cancel button
+
+ if ( !IsVisible() )
+ return;
+
+#ifndef _X360
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ // Force them to pick a class if they haven't picked one yet.
+ if ( pLocalPlayer && pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_UNDEFINED )
+ {
+ SetCancelButtonVisible( false );
+ SetVisibleButton( "ResetButton", false );
+ }
+
+ UpdateClassCounts();
+
+#endif
+
+ BaseClass::OnTick();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnClose()
+{
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ // Clear the HIDEHUD_HEALTH bit we hackily added. Turns out prediction
+ // was restoring these bits every frame. Unfortunately, prediction
+ // is off for karts which means the spell hud item would disappear if you
+ // brought up this menu and returned.
+ pLocalPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_HEALTH;
+ }
+
+ ShowPanel( false );
+
+ BaseClass::OnClose();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::SetVisible( bool state )
+{
+ BaseClass::SetVisible( state );
+
+ m_KeyRepeat.Reset();
+
+ if ( state )
+ {
+ engine->ServerCmd( "menuopen" ); // to the server
+ engine->ClientCmd( "_cl_classmenuopen 1" ); // for other panels
+ CBroadcastRecipientFilter filter;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_menu" );
+ }
+ else
+ {
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.class_menu" );
+ }
+
+ CheckMvMUpgrades();
+ }
+ else
+ {
+ engine->ServerCmd( "menuclosed" );
+ engine->ClientCmd( "_cl_classmenuopen 0" );
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.mvm_class_menu" );
+ }
+ else
+ {
+ CBaseEntity::StopSound( SOUND_FROM_UI_PANEL, "music.class_menu" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::Go()
+{
+ const int iClass = m_iCurrentClassIndex;
+ if ( iClass == TF_CLASS_UNDEFINED )
+ return;
+
+ // Check class limits
+ if ( TFGameRules() && !TFGameRules()->CanPlayerChooseClass( C_TFPlayer::GetLocalTFPlayer(), iClass ) )
+ return;
+
+#if defined( REPLAY_ENABLED )
+ // Display replay recording message if appropriate
+ int &nDisplayedConnectedRecording = CPlayerSpawnCache::Instance().m_Data.m_nDisplayedConnectedRecording;
+ if ( g_pReplay->IsReplayEnabled() &&
+ !g_pEngineClientReplay->IsPlayingReplayDemo() && // FIXME: We shouldn't need this here but for some reason the engine thinks a replay is recording during demo playback, even though replay_recording has a FCVAR_DONTRECORD flag
+ !nDisplayedConnectedRecording &&
+ replay_replaywelcomedlgcount.GetInt() <= MAX_TIMES_TO_SHOW_REPLAY_WELCOME_DLG )
+ {
+ wchar_t wText[256];
+ wchar wKeyBind[80];
+ char szText[256];
+
+ const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" );
+ if ( !pSaveReplayKey )
+ {
+ pSaveReplayKey = "< not bound >";
+ }
+ g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) );
+ g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_ConnectRecording" ), 1, wKeyBind );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) );
+
+ extern ConVar replay_msgduration_connectrecording;
+ g_pClientMode->DisplayReplayMessage( szText, replay_msgduration_connectrecording.GetFloat(), false, NULL, true );
+
+ // Don't execute this clause next time the player spawns, unless the cache has been cleared
+ ++nDisplayedConnectedRecording;
+
+ // Increment (archives)
+ replay_replaywelcomedlgcount.SetValue( replay_replaywelcomedlgcount.GetInt() + 1 );
+ }
+#endif
+
+ // This will complete any pending replay, commit if necessary, and clear - this way when the player respawns
+ // we will start with a fresh replay for the new life.
+ g_pClientReplayContext->OnPlayerClassChanged();
+
+ // Change class
+ BaseClass::OnCommand( CFmtStr( "joinclass %s", g_aRawPlayerClassNames[ iClass ] ).Access() );
+
+ CBroadcastRecipientFilter filter;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CBaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, "music.mvm_class_select" );
+ }
+ else
+ {
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnCommand( const char *command )
+{
+ if ( !V_strnicmp( command, "select", 6 ) )
+ {
+ const char *pClass = command + 6;
+ const int iClass = atoi( pClass );
+
+ // Avoid restarting the animation if the user selected on the same class
+ if ( iClass != m_iCurrentClassIndex )
+ {
+ SelectClass( iClass );
+ }
+ else
+ {
+ // Ensure selection states, in case the user clicks a button and drags away
+ UpdateButtonSelectionStates( iClass );
+ }
+
+ Go();
+ }
+ else if ( !V_strnicmp( command, "resetclass", 10 ) )
+ {
+ if ( TFGameRules() && !TFGameRules()->IsInHighlanderMode() )
+ return;
+
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+ else if ( !V_strnicmp( command, "openloadout", 11 ) )
+ {
+ // Let this panel know when you've closed, so we can reload items
+ EconUI()->AddPanelCloseListener( this );
+
+ // Make the back button close, rather than go back to the econ root panel
+ EconUI()->SetClosePanel( -m_iCurrentClassIndex );
+
+ // Set team number, so the model's color will match
+ EconUI()->SetDefaultTeam( GetTeamNumber() );
+
+ // Go directly to the loadout for the selected class
+ EconUI()->OpenEconUI( -m_iCurrentClassIndex );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Console command to select a class
+//-----------------------------------------------------------------------------
+void CTFClassMenu::Join_Class( const CCommand &args )
+{
+ if ( args.ArgC() > 1 )
+ {
+ char cmd[256];
+ Q_snprintf( cmd, sizeof( cmd ), "joinclass %s", args.Arg( 1 ) );
+ OnCommand( cmd );
+ ShowPanel( false );
+ }
+}
+
+static const char *g_sDialogVariables[] = {
+ "",
+ "numScout",
+ "numSoldier",
+ "numPyro",
+
+ "numDemoman",
+ "numHeavy",
+ "numEngineer",
+
+ "numMedic",
+ "numSniper",
+ "numSpy",
+ "",
+};
+
+static const char *g_sClassImagesBlue[] = {
+ "",
+ "class_sel_sm_scout_blu",
+ "class_sel_sm_soldier_blu",
+ "class_sel_sm_pyro_blu",
+
+ "class_sel_sm_demo_blu",
+ "class_sel_sm_heavy_blu",
+ "class_sel_sm_engineer_blu",
+
+ "class_sel_sm_medic_blu",
+ "class_sel_sm_sniper_blu",
+ "class_sel_sm_spy_blu",
+
+ "class_sel_sm_scout_blu",
+};
+
+static const char *g_sClassImagesRed[] = {
+ "",
+ "class_sel_sm_scout_red",
+ "class_sel_sm_soldier_red",
+ "class_sel_sm_pyro_red",
+
+ "class_sel_sm_demo_red",
+ "class_sel_sm_heavy_red",
+ "class_sel_sm_engineer_red",
+
+ "class_sel_sm_medic_red",
+ "class_sel_sm_sniper_red",
+ "class_sel_sm_spy_red",
+
+ "class_sel_sm_scout_red",
+};
+
+int g_ClassDefinesRemap[] = {
+ 0,
+ TF_CLASS_SCOUT,
+ TF_CLASS_SOLDIER,
+ TF_CLASS_PYRO,
+
+ TF_CLASS_DEMOMAN,
+ TF_CLASS_HEAVYWEAPONS,
+ TF_CLASS_ENGINEER,
+
+ TF_CLASS_MEDIC,
+ TF_CLASS_SNIPER,
+ TF_CLASS_SPY,
+ TF_CLASS_CIVILIAN,
+};
+
+void CTFClassMenu::UpdateNumClassLabels( int iTeam )
+{
+#ifndef _X360
+ int nTotalCount = 0;
+
+ // count how many of each class there are
+ if ( !g_TF_PR )
+ return;
+
+ if ( iTeam < FIRST_GAME_TEAM || iTeam >= TF_TEAM_COUNT ) // invalid team number
+ return;
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ bool bSpectator = pLocalPlayer && pLocalPlayer->GetTeamNumber() == TEAM_SPECTATOR;
+
+ int iLocalPlayerClass = TF_CLASS_UNDEFINED;
+ if ( pLocalPlayer )
+ {
+ iLocalPlayerClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ }
+
+ if ( iLocalPlayerClass == TF_CLASS_UNDEFINED )
+ {
+ m_iLocalPlayerClass = iLocalPlayerClass;
+
+ if ( m_pLocalPlayerImage && m_pLocalPlayerImage->IsVisible() )
+ {
+ m_pLocalPlayerImage->SetVisible( false );
+ }
+
+ if ( m_pLocalPlayerBG && m_pLocalPlayerBG->IsVisible() )
+ {
+ m_pLocalPlayerBG->SetVisible( false );
+ }
+ }
+
+ for( int i = TF_FIRST_NORMAL_CLASS ; i <= TF_LAST_NORMAL_CLASS ; i++ )
+ {
+ if ( bSpectator == true )
+ {
+ SetDialogVariable( g_sDialogVariables[i], "" );
+ continue;
+ }
+
+ int classCount = g_TF_PR->GetCountForPlayerClass( iTeam, g_ClassDefinesRemap[i], false );
+ int iClassLimit = TFGameRules()->GetClassLimit( g_ClassDefinesRemap[i] );
+
+ if ( iClassLimit != NO_CLASS_LIMIT )
+ {
+ if ( classCount >= iClassLimit )
+ {
+ if ( classCount > 0 )
+ {
+ wchar_t wTemp[32];
+ wchar_t wzCount[10];
+ _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount );
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitHit"), 1, wzCount );
+ SetDialogVariable( g_sDialogVariables[i], wTemp );
+ }
+ else
+ {
+ SetDialogVariable( g_sDialogVariables[i], g_pVGuiLocalize->Find("TF_ClassLimitHit_None") );
+ }
+ }
+ else
+ {
+ wchar_t wTemp[32];
+ wchar_t wzCount[10];
+ _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", classCount );
+ wchar_t wzMax[10];
+ _snwprintf( wzMax, ARRAYSIZE( wzMax ), L"%d", iClassLimit );
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find("TF_ClassLimitUnder"), 2, wzCount, wzMax );
+ SetDialogVariable( g_sDialogVariables[i], wTemp );
+ }
+ }
+ else if ( classCount > 0 )
+ {
+ SetDialogVariable( g_sDialogVariables[i], classCount );
+ }
+ else
+ {
+ SetDialogVariable( g_sDialogVariables[i], "" );
+ }
+
+ if ( g_ClassDefinesRemap[i] == iLocalPlayerClass )
+ {
+ // take 1 off the count for the images since the local player has their own image already
+ if ( classCount > 0 )
+ {
+ classCount--;
+ }
+
+ if ( m_pLocalPlayerImage )
+ {
+ if ( !m_pLocalPlayerImage->IsVisible() )
+ {
+ m_pLocalPlayerImage->SetVisible( true );
+ }
+
+ if ( m_iLocalPlayerClass != iLocalPlayerClass )
+ {
+ m_iLocalPlayerClass = iLocalPlayerClass;
+ m_pLocalPlayerImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] );
+ }
+ }
+
+ if ( m_pLocalPlayerBG && !m_pLocalPlayerBG->IsVisible() )
+ {
+ m_pLocalPlayerBG->SetVisible( true );
+ }
+ }
+
+ if ( nTotalCount < CLASS_COUNT_IMAGES )
+ {
+ for ( int j = 0 ; j < classCount ; ++j )
+ {
+ CTFImagePanel *pImage = m_ClassCountImages[nTotalCount];
+ if ( pImage )
+ {
+ pImage->SetVisible( true );
+ pImage->SetImage( iTeam == TF_TEAM_BLUE ? g_sClassImagesBlue[i] : g_sClassImagesRed[i] );
+ }
+
+ nTotalCount++;
+ if ( nTotalCount >= CLASS_COUNT_IMAGES )
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if ( nTotalCount == 0 )
+ {
+ // no classes for our team yet
+ if ( m_pCountLabel && m_pCountLabel->IsVisible() )
+ {
+ m_pCountLabel->SetVisible( false );
+ }
+ }
+ else
+ {
+ if ( m_pCountLabel && !m_pCountLabel->IsVisible() )
+ {
+ m_pCountLabel->SetVisible( true );
+ }
+ }
+
+ // turn off any unused images
+ while ( nTotalCount < CLASS_COUNT_IMAGES )
+ {
+ CTFImagePanel *pImage = m_ClassCountImages[nTotalCount];
+ if ( pImage )
+ {
+ pImage->SetVisible( false );
+ }
+
+ nTotalCount++;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::FireGameEvent( IGameEvent *event )
+{
+ const char *pszEventName = event->GetName();
+
+ // when we are changing levels
+ if ( FStrEq( pszEventName, "localplayer_changeteam" ) )
+ {
+ if ( IsVisible() )
+ {
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ int iTeam = pLocalPlayer->GetTeamNumber();
+ if ( iTeam != GetTeamNumber() )
+ {
+ ShowPanel( false );
+
+ if ( iTeam == TF_TEAM_BLUE )
+ {
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true );
+ }
+ else
+ {
+ gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true );
+ }
+ }
+ }
+ }
+ }
+ else if ( FStrEq( pszEventName, "show_match_summary" ) )
+ {
+ if ( IsVisible() )
+ {
+ ShowPanel( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClassMenu::OnEconUIClosed()
+{
+ // Reload items on model panel, in case anything's changed
+ LoadItems();
+}
+
+int g_nNumUpgradeIconsForLastHint = 0;
+
+//-----------------------------------------------------------------------------
+void CTFClassMenu::CheckMvMUpgrades()
+{
+ // Set MvM Icons invisible
+ for ( int icons = 0; icons < ARRAYSIZE( m_pMvmUpgradeImages ); ++icons )
+ {
+ if ( m_pMvmUpgradeImages[icons] == NULL )
+ continue;
+ m_pMvmUpgradeImages[icons]->SetVisible( false );
+ }
+
+ if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() )
+ return;
+
+ CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance();
+ if ( !pStats )
+ return;
+
+ CUtlVector< CUpgradeInfo > *upgrades = pStats->GetLocalPlayerUpgrades();
+
+ int nShowUpgradingHint = -1;
+ int nNumUpgradeIconsForHint = 0;
+
+ for ( int i = 0; i < upgrades->Count(); ++i )
+ {
+ vgui::Panel *pUpgradeImage = m_pMvmUpgradeImages[upgrades->Element(i).m_iPlayerClass];
+
+ if ( !pUpgradeImage )
+ continue;
+
+ if ( !pUpgradeImage->IsVisible() )
+ {
+ pUpgradeImage->SetVisible( true );
+ nNumUpgradeIconsForHint++;
+
+ // Only show the hint if we've shown it 3 or less times ever
+ if ( nShowUpgradingHint == -1 && tf_mvm_classupgradehelpcount.GetInt() < 3 )
+ {
+ int nY;
+ pUpgradeImage->GetPos( nShowUpgradingHint, nY );
+ nShowUpgradingHint += pUpgradeImage->GetWide() / 2;
+ }
+ }
+ }
+
+ // Only show the hint if there are more upgrade icon than the last time we openned the menu
+ if ( nShowUpgradingHint != -1 && g_nNumUpgradeIconsForLastHint < nNumUpgradeIconsForHint )
+ {
+ CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->SetCalloutInParentsX( nShowUpgradingHint );
+ pPopup->Popup();
+
+ g_nNumUpgradeIconsForLastHint = nNumUpgradeIconsForHint;
+ tf_mvm_classupgradehelpcount.SetValue( tf_mvm_classupgradehelpcount.GetInt() + 1 );
+ }
+ }
+}
+
+
diff --git a/game/client/tf/vgui/tf_classmenu.h b/game/client/tf/vgui/tf_classmenu.h
new file mode 100644
index 0000000..8d908da
--- /dev/null
+++ b/game/client/tf/vgui/tf_classmenu.h
@@ -0,0 +1,177 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CLASSMENU_H
+#define TF_CLASSMENU_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <classmenu.h>
+#include <vgui_controls/EditablePanel.h>
+#include "vgui_controls/KeyRepeat.h"
+#include <filesystem.h>
+#include <tf_shareddefs.h>
+#include "cbase.h"
+#include "tf_controls.h"
+#include "tf_gamerules.h"
+#include "basemodelpanel.h"
+#include "IconPanel.h"
+#include <vgui_controls/CheckButton.h>
+#include "GameEventListener.h"
+#include "c_tf_playerresource.h"
+#include "tf_playermodelpanel.h"
+#include "tf_mann_vs_machine_stats.h"
+
+using namespace vgui;
+
+#define CLASS_COUNT_IMAGES 11
+
+class CTFClassTipsPanel;
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+class CTFClassTipsItemPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFClassTipsItemPanel, vgui::EditablePanel );
+
+public:
+ CTFClassTipsItemPanel( Panel *parent, const char *pszName, int iListItemID );
+ ~CTFClassTipsItemPanel();
+
+ void SetClassTip( const wchar_t *pwszText, const char *pszIcon );
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+
+private:
+ vgui::ImagePanel *m_pTipIcon;
+ CExLabel *m_pTipLabel;
+};
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+class CTFClassMenu : public CClassMenu, public CGameEventListener
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClassMenu, CClassMenu );
+
+public:
+ CTFClassMenu( IViewPort *pViewPort );
+
+ virtual void Update( void );
+ virtual Panel *CreateControlByName( const char *controlName );
+ virtual void OnTick( void );
+ virtual void PaintBackground( void );
+ virtual void SetVisible( bool state );
+ virtual void PerformLayout();
+
+ MESSAGE_FUNC_PTR_CHARPTR( OnShowPage, "ShowPage", panel, page );
+ CON_COMMAND_MEMBER_F( CTFClassMenu, "join_class", Join_Class, "Send a joinclass command", 0 );
+
+ virtual void OnCommand( const char *command );
+ virtual void OnClose();
+ virtual void ShowPanel( bool bShow );
+ virtual void UpdateClassCounts( void ){}
+ void SelectClass( int iClass );
+
+ virtual int GetTeamNumber( void ) = 0;
+
+ // IGameEventListener interface:
+ virtual void FireGameEvent( IGameEvent *event );
+
+ MESSAGE_FUNC( OnEconUIClosed, "EconUIClosed" ); // If the econ UI was opened (for editing loadout), we'll get notified when the user's done.
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; }
+
+protected:
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void OnKeyCodePressed( KeyCode code );
+ CExImageButton *GetCurrentClassButton();
+ virtual void OnKeyCodeReleased( vgui::KeyCode code );
+ virtual void OnThink();
+ virtual void UpdateNumClassLabels( int iTeam );
+
+ void UpdateButtonSelectionStates( int iClass );
+ void SetCancelButtonVisible( bool bVisible );
+ int GetCurrentPlayerClass();
+ void LoadItems();
+ void Go();
+
+protected:
+
+ CExImageButton *m_pClassButtons[TF_CLASS_MENU_BUTTONS];
+ vgui::ImagePanel *m_pMvmUpgradeImages[TF_CLASS_MENU_BUTTONS];
+ CSCHintIcon *m_pClassHintIcons[TF_CLASS_MENU_BUTTONS];
+
+ CTFClassTipsPanel *m_pClassTipsPanel;
+ CTFPlayerModelPanel *m_pTFPlayerModelPanel;
+ CExButton *m_pEditLoadoutButton;
+ CExLabel *m_pSelectAClassLabel;
+ CExplanationPopup *m_pClassHighlightPanel;
+ CSCHintIcon *m_pEditLoadoutHintIcon;
+ CSCHintIcon *m_pCancelHintIcon;
+
+private:
+
+ void CheckMvMUpgrades();
+
+#ifdef _X360
+ CTFFooter *m_pFooter;
+#endif
+
+ ButtonCode_t m_iClassMenuKey;
+ int m_iCurrentClassIndex;
+ vgui::CKeyRepeatHandler m_KeyRepeat;
+
+ int m_nBaseMusicGuid;
+
+#ifndef _X360
+ CTFImagePanel *m_ClassCountImages[CLASS_COUNT_IMAGES];
+ CExLabel *m_pCountLabel;
+ CTFImagePanel *m_pLocalPlayerImage;
+ CTFImagePanel *m_pLocalPlayerBG;
+ int m_iLocalPlayerClass;
+#endif
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Draws the blue class menu
+//-----------------------------------------------------------------------------
+
+class CTFClassMenu_Blue : public CTFClassMenu
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClassMenu_Blue, CTFClassMenu );
+
+public:
+ CTFClassMenu_Blue( IViewPort *pViewPort ) : BaseClass( pViewPort ) {}
+
+ virtual const char *GetName( void ) { return PANEL_CLASS_BLUE; }
+ virtual int GetTeamNumber( void ) { return TF_TEAM_BLUE; }
+ virtual void UpdateClassCounts( void ){ UpdateNumClassLabels( TF_TEAM_BLUE ); }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Draws the red class menu
+//-----------------------------------------------------------------------------
+
+class CTFClassMenu_Red : public CTFClassMenu
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClassMenu_Red, CTFClassMenu );
+
+public:
+ CTFClassMenu_Red( IViewPort *pViewPort ) : BaseClass( pViewPort ) {}
+
+ virtual const char *GetName( void ) { return PANEL_CLASS_RED; }
+ virtual int GetTeamNumber( void ) { return TF_TEAM_RED; }
+ virtual void UpdateClassCounts( void ){ UpdateNumClassLabels( TF_TEAM_RED ); }
+};
+
+#endif // TF_CLASSMENU_H
+
diff --git a/game/client/tf/vgui/tf_clientscoreboard.cpp b/game/client/tf/vgui/tf_clientscoreboard.cpp
new file mode 100644
index 0000000..2cbdfe0
--- /dev/null
+++ b/game/client/tf/vgui/tf_clientscoreboard.cpp
@@ -0,0 +1,2424 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include <tier1/fmtstr.h>
+#include <vgui_controls/Label.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui_controls/RichText.h>
+#include <vgui_controls/Frame.h>
+#include <vgui/IScheme.h>
+#include <vgui/ILocalize.h>
+#include <vgui/ISurface.h>
+#include <vgui/IVGui.h>
+#include <vgui_controls/SectionedListPanel.h>
+#include <vgui_controls/ImageList.h>
+#include <game/client/iviewport.h>
+#include <KeyValues.h>
+#include <filesystem.h>
+#include "IGameUIFuncs.h" // for key bindings
+#include "VGuiMatSurface/IMatSystemSurface.h"
+
+#include "tf_controls.h"
+#include "tf_shareddefs.h"
+#include "tf_playermodelpanel.h"
+#include "tf_clientscoreboard.h"
+#include "c_playerresource.h"
+#include "c_tf_playerresource.h"
+#include "c_tf_team.h"
+#include "c_tf_player.h"
+#include "vgui_avatarimage.h"
+#include "tf_gamerules.h"
+#include "econ_item_description.h"
+#include "vgui_controls/MenuItem.h"
+#include "vgui/IInput.h"
+#include "voice_status.h"
+#include "vgui_controls/ScrollBarSlider.h"
+#include "econ/econ_trading.h"
+#include "in_buttons.h"
+#include "tf_mapinfo.h"
+
+#if defined ( _X360 )
+#include "engine/imatchmaking.h"
+#endif
+
+#if defined( REPLAY_ENABLED )
+#include "replay/ireplaysystem.h"
+#include "replay/ienginereplay.h"
+#endif
+
+#include "econ_item_system.h"
+#include "tf_mann_vs_machine_stats.h"
+#include "player_vs_environment/c_tf_upgrades.h"
+#include "tf_badge_panel.h"
+#include "report_player_dialog.h"
+
+using namespace vgui;
+
+#define SCOREBOARD_MAX_LIST_ENTRIES 12
+
+ConVar tf_scoreboard_mouse_mode( "tf_scoreboard_mouse_mode", "0", FCVAR_ARCHIVE );
+
+
+void cc_scoreboard_convar_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ CTFClientScoreBoardDialog *pScoreboard = dynamic_cast< CTFClientScoreBoardDialog *>( gViewPortInterface->FindPanelByName( PANEL_SCOREBOARD ) );
+ if ( pScoreboard )
+ {
+ pScoreboard->Reset();
+ }
+}
+ConVar tf_scoreboard_ping_as_text( "tf_scoreboard_ping_as_text", "0", FCVAR_ARCHIVE, "Show ping values as text in the scoreboard.", cc_scoreboard_convar_changed );
+ConVar tf_scoreboard_alt_class_icons( "tf_scoreboard_alt_class_icons", "0", FCVAR_ARCHIVE, "Show alternate class icons in the scoreboard." );
+
+extern bool IsInCommentaryMode( void );
+extern bool DuelMiniGame_GetStats( C_TFPlayer **ppPlayer, uint32 &unMyScore, uint32 &unOpponentScore );
+extern void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
+
+extern ConVar cl_hud_playerclass_use_playermodel;
+extern ConVar mp_tournament;
+
+//......................................................
+enum
+{
+ PING_LOW,
+ PING_MED,
+ PING_HIGH,
+ PING_VERY_HIGH,
+ PING_BOT_RED,
+ PING_BOT_BLUE,
+
+ PING_MAX_TYPES
+};
+static const char *pszPingIcons[SCOREBOARD_PING_ICONS] = {
+ "../hud/scoreboard_ping_low",
+ "../hud/scoreboard_ping_med",
+ "../hud/scoreboard_ping_high",
+ "../hud/scoreboard_ping_very_high",
+ "../hud/scoreboard_ping_bot_red",
+ "../hud/scoreboard_ping_bot_blue",
+};
+static const char *pszPingIconsDead[SCOREBOARD_PING_ICONS] = {
+ "../hud/scoreboard_ping_low_d",
+ "../hud/scoreboard_ping_med_d",
+ "../hud/scoreboard_ping_high_d",
+ "../hud/scoreboard_ping_very_high_d",
+ "../hud/scoreboard_ping_bot_red_d",
+ "../hud/scoreboard_ping_bot_blue_d",
+};
+COMPILE_TIME_ASSERT( SCOREBOARD_PING_ICONS == PING_MAX_TYPES );
+//......................................................
+
+static const char *pszDominationIcons[SCOREBOARD_DOMINATION_ICONS] = {
+ "",
+ "../hud/leaderboard_dom1",
+ "../hud/leaderboard_dom2",
+ "../hud/leaderboard_dom3",
+ "../hud/leaderboard_dom4",
+ "../hud/leaderboard_dom5",
+ "../hud/leaderboard_dom6",
+ "../hud/leaderboard_dom7",
+ "../hud/leaderboard_dom8",
+ "../hud/leaderboard_dom9",
+ "../hud/leaderboard_dom10",
+ "../hud/leaderboard_dom11",
+ "../hud/leaderboard_dom12",
+ "../hud/leaderboard_dom13",
+ "../hud/leaderboard_dom14",
+ "../hud/leaderboard_dom15",
+ "../hud/leaderboard_dom16",
+};
+
+static const char *pszDominationIconsDead[SCOREBOARD_DOMINATION_ICONS] = {
+ "",
+ "../hud/leaderboard_dom1_d",
+ "../hud/leaderboard_dom2_d",
+ "../hud/leaderboard_dom3_d",
+ "../hud/leaderboard_dom4_d",
+ "../hud/leaderboard_dom5_d",
+ "../hud/leaderboard_dom6_d",
+ "../hud/leaderboard_dom7_d",
+ "../hud/leaderboard_dom8_d",
+ "../hud/leaderboard_dom9_d",
+ "../hud/leaderboard_dom10_d",
+ "../hud/leaderboard_dom11_d",
+ "../hud/leaderboard_dom12_d",
+ "../hud/leaderboard_dom13_d",
+ "../hud/leaderboard_dom14_d",
+ "../hud/leaderboard_dom15_d",
+ "../hud/leaderboard_dom16_d",
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFClientScoreBoardDialog::CTFClientScoreBoardDialog( IViewPort *pViewPort ) : CClientScoreBoardDialog( pViewPort )
+{
+ SetProportional( true );
+ SetKeyBoardInputEnabled( false );
+ SetScheme( "ClientScheme" );
+
+ m_pPlayerListBlue = new SectionedListPanel( this, "BluePlayerList" );
+ m_pPlayerListRed = new SectionedListPanel( this, "RedPlayerList" );
+ m_pLabelPlayerName = new CExLabel( this, "PlayerNameLabel", "" );
+ m_pImagePanelHorizLine = new ImagePanel( this, "HorizontalLine" );
+ m_pClassImage = new CTFClassImage( this, "ClassImage" );
+ m_pPlayerModelPanel = new CTFPlayerModelPanel( this, "classmodelpanel" );
+ m_pLocalPlayerStatsPanel = new vgui::EditablePanel( this, "LocalPlayerStatsPanel" );
+ m_pLocalPlayerDuelStatsPanel = new vgui::EditablePanel( this, "LocalPlayerDuelStatsPanel" );
+ m_duelPanelLocalPlayer.m_pPanel = new vgui::EditablePanel( m_pLocalPlayerDuelStatsPanel, "LocalPlayerData" );
+ m_duelPanelOpponent.m_pPanel = new vgui::EditablePanel( m_pLocalPlayerDuelStatsPanel, "OpponentData" );
+ m_pRedTeamName = new CExLabel( this, "RedTeamLabel", "" );
+ m_pBlueTeamName = new CExLabel( this, "BlueTeamLabel", "" );
+ m_pRedLeaderAvatarImage = new CAvatarImagePanel( this, "RedLeaderAvatar" );
+ m_pRedLeaderAvatarBG = new EditablePanel( this, "RedLeaderAvatarBG" );
+ m_pRedTeamImage = new ImagePanel( this, "RedTeamImage" );
+ m_pBlueLeaderAvatarImage = new CAvatarImagePanel( this, "BlueLeaderAvatar" );
+ m_pBlueLeaderAvatarBG = new EditablePanel( this, "BlueLeaderAvatarBG" );
+ m_pBlueTeamImage = new ImagePanel( this, "BlueTeamImage" );
+
+ m_pServerTimeLeftValue = NULL;
+ m_pFontTimeLeftNumbers = vgui::INVALID_FONT;
+ m_pFontTimeLeftString = vgui::INVALID_FONT;
+
+ m_pMvMScoreboard = new CTFHudMannVsMachineScoreboard( this, "MvMScoreboard" );
+ m_pRightClickMenu = NULL;
+
+ m_iImageDominated = 0;
+ m_iImageDominatedDead = 0;
+ m_iImageNemesis = 0;
+ m_iImageNemesisDead = 0;
+ m_iImageStreak = 0;
+ m_iImageStreakDead = 0;
+ m_iTextureCamera = -1;
+
+ m_bIsPVEMode = false;
+// m_bDisplayLevel = false;
+ m_bMouseActivated = true;
+
+ Q_memset( m_iImageClass, NULL, sizeof( m_iImageClass ) );
+ Q_memset( m_iImageClassAlt, NULL, sizeof( m_iImageClassAlt ) );
+ Q_memset( m_iImageDom, NULL, sizeof( m_iImageDom ) );
+ Q_memset( m_iImageDomDead, NULL, sizeof( m_iImageDomDead ) );
+
+ ListenForGameEvent( "server_spawn" );
+#ifdef STAGING_ONLY
+// ListenForGameEvent( "bountymode_toggled" );
+#endif
+
+ SetDialogVariable( "server", "" );
+ SetVisible( false );
+
+ m_nPlayerModelPanelIndex = -1;
+ m_bRedScrollBarVisible = false;
+ m_bBlueScrollBarVisible = false;
+ m_nExtraSpace = 0;
+ m_hSelectedPlayer = NULL;
+ m_bUsePlayerModel = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CTFClientScoreBoardDialog::~CTFClientScoreBoardDialog()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::PerformLayout()
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdatePlayerModel()
+{
+ if ( !m_pPlayerModelPanel->IsVisible() )
+ return;
+
+ if ( m_nPlayerModelPanelIndex == -1 )
+ {
+ m_nPlayerModelPanelIndex = GetLocalPlayerIndex();
+ }
+
+ C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( m_nPlayerModelPanelIndex ) );
+ if ( !pPlayer )
+ return;
+
+ int nClass = pPlayer->GetPlayerClass()->GetClassIndex();
+ int nTeam = pPlayer->GetTeamNumber();
+ int nItemSlot = ( pPlayer->IsAlive() && pPlayer->GetActiveTFWeapon() ) ? pPlayer->GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( nClass ) : LOADOUT_POSITION_PRIMARY;;
+ CEconItemView *pWeapon = NULL;
+
+ CTFWeaponBase *pEnt = dynamic_cast<CTFWeaponBase*>( pPlayer->GetEntityForLoadoutSlot( nItemSlot ) );
+ if ( pEnt )
+ {
+ pWeapon = pEnt->GetAttributeContainer()->GetItem();
+ }
+
+ bool bIsRobot = false;
+ int iRobot = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iRobot, appear_as_mvm_robot );
+ bIsRobot = iRobot ? true : false;
+
+ m_pPlayerModelPanel->ClearCarriedItems();
+ m_pPlayerModelPanel->SetToPlayerClass( nClass, bIsRobot );
+ m_pPlayerModelPanel->SetTeam( nTeam );
+
+ if ( pWeapon )
+ {
+ m_pPlayerModelPanel->AddCarriedItem( pWeapon );
+ }
+
+ for ( int wbl = pPlayer->GetNumWearables() - 1; wbl >= 0; wbl-- )
+ {
+ C_TFWearable *pItem = dynamic_cast<C_TFWearable*>( pPlayer->GetWearable( wbl ) );
+ if ( !pItem )
+ continue;
+
+ if ( pItem->IsViewModelWearable() )
+ continue;
+
+ if ( pItem->IsDisguiseWearable() )
+ continue;
+
+ CAttributeContainer *pCont = pItem->GetAttributeContainer();
+ CEconItemView *pEconItemView = pCont ? pCont->GetItem() : NULL;
+
+ if ( pEconItemView && pEconItemView->IsValid() )
+ {
+ m_pPlayerModelPanel->AddCarriedItem( pEconItemView );
+ }
+ }
+
+ m_pPlayerModelPanel->HoldItemInSlot( nItemSlot );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ KeyValues *pConditions = NULL;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ pConditions = new KeyValues( "conditions" );
+ AddSubKeyNamed( pConditions, "if_mvm" );
+ }
+
+ LoadControlSettings( "Resource/UI/Scoreboard.res", NULL, NULL, pConditions );
+ m_hScoreFontDefault = pScheme->GetFont( "Default", true );
+ m_hScoreFontSmallest = pScheme->GetFont( "ScoreboardSmallest", true );
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ }
+
+ if ( m_pImageList )
+ {
+ m_iImageDominated = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_dominated", true ) );
+ m_iImageDominatedDead = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_dominated_d", true ) );
+ m_iImageNemesis = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_nemesis", true ) );
+ m_iImageNemesisDead = m_pImageList->AddImage( scheme()->GetImage( "../hud/leaderboard_nemesis_d", true ) );
+ m_iImageStreak = m_pImageList->AddImage( scheme()->GetImage( "../hud/scoreboard_streak", true ) );
+ m_iImageStreakDead = m_pImageList->AddImage( scheme()->GetImage( "../hud/scoreboard_streak_d", true ) );
+
+ for(int i = 1 ; i < SCOREBOARD_DOMINATION_ICONS ; i++)
+ {
+ m_iImageDom[i] = m_pImageList->AddImage( scheme()->GetImage( pszDominationIcons[i], true ) );
+ m_iImageDomDead[i] = m_pImageList->AddImage( scheme()->GetImage( pszDominationIconsDead[i], true ) );
+ }
+ for( int i = 1 ; i < SCOREBOARD_CLASS_ICONS ; i++ )
+ {
+ m_iImageClass[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIcons[i], true ) );
+ m_iImageClassAlt[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIconsAlt[i], true ) );
+ }
+ for ( int i = 0; i < SCOREBOARD_PING_ICONS; i++ )
+ {
+ m_iImagePing[i] = m_pImageList->AddImage( scheme()->GetImage( pszPingIcons[i], true ) );
+ m_iImagePingDead[i] = m_pImageList->AddImage( scheme()->GetImage( pszPingIconsDead[i], true ) );
+ }
+
+ // resize the images to our resolution
+ for (int i = 1 ; i < m_pImageList->GetImageCount(); i++ )
+ {
+ int wide = 13, tall = 13;
+ m_pImageList->GetImage(i)->SetSize(scheme()->GetProportionalScaledValueEx( GetScheme(), wide ), scheme()->GetProportionalScaledValueEx( GetScheme(),tall ) );
+ }
+ }
+
+ SetPlayerListImages( m_pPlayerListBlue );
+ SetPlayerListImages( m_pPlayerListRed );
+
+ SetBgColor( Color( 0, 0, 0, 0) );
+ SetBorder( NULL );
+ SetVisible( false );
+
+ m_duelPanelLocalPlayer.m_pAvatar = dynamic_cast< CAvatarImagePanel *>( m_duelPanelLocalPlayer.m_pPanel->FindChildByName("AvatarImage") );
+ m_duelPanelLocalPlayer.m_pPlayerNameLabel = dynamic_cast< CExLabel *>( m_duelPanelLocalPlayer.m_pPanel->FindChildByName("AvatarTextLabel") );
+ m_duelPanelOpponent.m_pAvatar = dynamic_cast< CAvatarImagePanel *>( m_duelPanelOpponent.m_pPanel->FindChildByName("AvatarImage") );
+ m_duelPanelOpponent.m_pPlayerNameLabel = dynamic_cast< CExLabel *>( m_duelPanelOpponent.m_pPanel->FindChildByName("AvatarTextLabel") );
+
+ if ( m_pLocalPlayerStatsPanel )
+ {
+ m_pKillsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Kills" ) );
+ m_pDeathsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Deaths" ) );
+ m_pAssistLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Assists" ) );
+ m_pDestructionLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Destruction" ) );
+ m_pCapturesLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Captures" ) );
+ m_pDefensesLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Defenses" ) );
+ m_pDominationsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Domination" ) );
+ m_pRevengeLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Revenge" ) );
+ m_pHealingLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Healing" ) );
+ m_pInvulnsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Invuln" ) );
+ m_pTeleportsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Teleports" ) );
+ m_pHeadshotsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Headshots" ) );
+ m_pBackstabsLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Backstabs" ) );
+ m_pBonusLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Bonus" ) );
+ m_pSupportLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Support" ) );
+ m_pDamageLabel = dynamic_cast< CExLabel* >( m_pLocalPlayerStatsPanel->FindChildByName( "Damage" ) );
+ }
+
+ m_pServerTimeLeftValue = dynamic_cast< CExLabel* >( FindChildByName( "ServerTimeLeftValue" ) );
+ m_pFontTimeLeftNumbers = pScheme->GetFont( "ScoreboardMediumSmall", true );
+ m_pFontTimeLeftString = pScheme->GetFont( "ScoreboardVerySmall", true );
+
+ Reset();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::ShowPanel( bool bShow )
+{
+ if ( bShow )
+ {
+ if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
+ return;
+ }
+
+ // Catch the case where we call ShowPanel before ApplySchemeSettings, eg when
+ // going from windowed <-> fullscreen
+ if ( m_pImageList == NULL )
+ {
+ InvalidateLayout( true, true );
+ }
+
+ bool bIsPVEMode = TFGameRules() && TFGameRules()->IsPVEModeActive();
+ if ( m_bIsPVEMode != bIsPVEMode )
+ {
+ m_bIsPVEMode = bIsPVEMode;
+ InvalidateLayout( true, true );
+ }
+
+ // Don't show in commentary mode
+ if ( IsInCommentaryMode() )
+ {
+ bShow = false;
+ }
+
+ if ( IsVisible() == bShow )
+ {
+ return;
+ }
+
+ int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "global" );
+
+ if ( bShow )
+ {
+ SetVisible( true );
+ MoveToFront();
+ InitializeInputScheme();
+
+ gHUD.LockRenderGroup( iRenderGroup );
+
+ // Clear the selected item, this forces the default to the local player
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ pList->ClearSelection();
+ }
+
+ if ( tf_scoreboard_mouse_mode.GetInt() == 2 )
+ m_bMouseActivated = false;
+
+ UpdateServerTimeLeft();
+
+ m_nPlayerModelPanelIndex = -1;
+ m_hSelectedPlayer = NULL;
+ UpdatePlayerModel();
+ }
+ else
+ {
+ SetVisible( false );
+
+ if ( m_pRightClickMenu )
+ {
+ delete m_pRightClickMenu;
+ m_pRightClickMenu = NULL;
+ }
+
+ gHUD.UnlockRenderGroup( iRenderGroup );
+ m_bMouseActivated = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::OnCommand( const char *command )
+{
+ if ( V_stristr( command, "report_player" ) )
+ {
+ engine->ClientCmd( command );
+ }
+ else if ( !V_strcmp( command, "muteplayer" ) )
+ {
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem );
+ if ( !pIssueKeyValues )
+ return;
+
+ int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 );
+ if ( playerIndex > 0 && playerIndex <= MAX_PLAYERS && GetClientVoiceMgr() )
+ {
+ // Toggle
+ GetClientVoiceMgr()->SetPlayerBlockedState( playerIndex, ( GetClientVoiceMgr()->IsPlayerBlocked( playerIndex ) ? false : true ) );
+ }
+ }
+ }
+ }
+ else if ( !V_strcmp( command, "specplayer" ) )
+ {
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem );
+ if ( !pIssueKeyValues )
+ return;
+
+ int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 );
+ if ( playerIndex > 0 && playerIndex <= MAX_PLAYERS )
+ {
+ engine->ClientCmd_Unrestricted( VarArgs( "spec_player %d\n", playerIndex ) );
+ }
+ }
+ }
+ }
+ else if ( !V_strcmp( command, "friendadd" ) )
+ {
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem );
+ if ( !pIssueKeyValues )
+ return;
+
+ int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 );
+ CBasePlayer *pTarget = UTIL_PlayerByIndex( playerIndex );
+ if ( pTarget && !( pTarget->IsBot() || pTarget->IsHLTV() ) )
+ {
+ CSteamID steamID;
+ if ( pTarget->GetSteamID( &steamID ) && steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "friendadd", steamID );
+ }
+ }
+ }
+ }
+ }
+ else if ( !V_strcmp( command, "jointrade" ) )
+ {
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem );
+ if ( !pIssueKeyValues )
+ return;
+
+ int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 );
+ CBasePlayer *pTarget = UTIL_PlayerByIndex( playerIndex );
+ if ( pTarget && !( pTarget->IsBot() || pTarget->IsHLTV() ) )
+ {
+ // Prevent large UI popup during a match
+ if ( pTarget->GetTeamNumber() >= FIRST_GAME_TEAM )
+ {
+ if ( TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
+ return;
+ }
+
+ Trading_RequestTrade( playerIndex );
+ }
+ }
+ }
+ }
+ else if ( !V_strcmp( command, "profile" ) )
+ {
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem );
+ if ( !pIssueKeyValues )
+ return;
+
+ int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 );
+ CBasePlayer *pTarget = UTIL_PlayerByIndex( playerIndex );
+ if ( pTarget && !( pTarget->IsBot() || pTarget->IsHLTV() ) )
+ {
+ CSteamID steamID;
+ if ( pTarget->GetSteamID( &steamID ) && steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "steamid", steamID );
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::OnItemSelected( vgui::Panel *panel )
+{
+ if ( panel == m_pPlayerListBlue || panel == m_pPlayerListRed )
+ {
+ // There can be only one... selection
+ if ( panel == m_pPlayerListBlue && m_pPlayerListBlue->GetSelectedItem() >= 0 && m_pPlayerListRed->GetSelectedItem() >= 0 )
+ {
+ m_pPlayerListRed->ClearSelection();
+ }
+ else if ( panel == m_pPlayerListRed && m_pPlayerListRed->GetSelectedItem() >= 0 && m_pPlayerListBlue->GetSelectedItem() >= 0 )
+ {
+ m_pPlayerListBlue->ClearSelection();
+ }
+
+ if ( vgui::input()->IsMouseDown( MOUSE_RIGHT ) )
+ {
+ OnScoreBoardMouseRightRelease();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::OnItemContextMenu( vgui::Panel *panel )
+{
+ if ( panel == m_pPlayerListBlue || panel == m_pPlayerListRed )
+ {
+ if ( vgui::input()->IsMouseDown( MOUSE_RIGHT ) )
+ {
+ OnScoreBoardMouseRightRelease();
+ }
+ }
+}
+
+void CTFClientScoreBoardDialog::OnReportPlayer( KeyValues *pData )
+{
+ int playerIndex = pData->GetInt( "playerIndex" );
+ int nReason = pData->GetInt( "reason" );
+ CSteamID steamID = GetSteamIDForPlayerIndex( playerIndex );
+ ReportPlayerAccount( steamID, nReason );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Figure out where to put the camera view
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::GetCameraUnderlayBounds( int *pX, int *pY, int *pWide, int *pTall )
+{
+ GetBounds( *pX, *pY, *pWide, *pTall );
+
+ // inset these b*pY a bit
+ *pX += 15;
+ *pY += 25;
+ *pWide -= 30;
+ *pTall -= 40;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::OnScoreBoardMouseRightRelease( void )
+{
+ if ( !IsVisible() )
+ return;
+
+ C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalTFPlayer )
+ return;
+
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( !pList )
+ return;
+
+ int iSelectedItem = pList->GetSelectedItem();
+ if ( iSelectedItem < 0 )
+ return;
+
+ KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem );
+ if ( !pIssueKeyValues )
+ return;
+
+ int playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 );
+ if ( GetLocalPlayerIndex() == playerIndex )
+ return;
+
+ if ( m_pRightClickMenu )
+ delete m_pRightClickMenu;
+
+ const char *pszContextMenuBorder = "DarkComboBoxBorder";
+ const char *pszContextMenuFont = "HudFontMediumSecondary";
+ m_pRightClickMenu = new Menu( pList, "RightClickMenu" );
+ m_pRightClickMenu->SetKeyBoardInputEnabled( false );
+ m_pRightClickMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ m_pRightClickMenu->SetFgColor( Color( 0, 0, 255, 255 ) );
+ m_pRightClickMenu->SetPaintBackgroundEnabled( true );
+ m_pRightClickMenu->SetPaintBackgroundType( 0 );
+ m_pRightClickMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+ bool bFakeClient = ( g_TF_PR->IsFakePlayer( playerIndex ) );
+ bool bTournamentGame = ( g_TF_PR->GetTeam( playerIndex ) >= FIRST_GAME_TEAM && TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING );
+
+ MenuBuilder contextMenuBuilder( m_pRightClickMenu, this );
+
+ // Report
+ if ( !bFakeClient && engine->GetLocalPlayer() != playerIndex )
+ {
+ Menu *pReportSubMenu = new Menu( this, "ReportSubMenu" );
+ pReportSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pReportSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ contextMenuBuilder.AddCascadingMenuItem( "#TF_ScoreBoard_Context_Report", pReportSubMenu, "report" );
+ for ( int iReason=CMsgGC_ReportPlayer_EReason_kReason_INVALID+1; iReason<CMsgGC_ReportPlayer_EReason_kReason_COUNT; ++iReason )
+ {
+ CFmtStr name( "#TF_ScoreBoard_Context_Report_Reason%d", iReason );
+ KeyValues *pReport = new KeyValues( "ReportPlayer" );
+ pReport->SetInt( "playerIndex", playerIndex );
+ pReport->SetInt( "reason", iReason );
+ pReportSubMenu->AddMenuItem( name, pReport, this );
+ }
+ }
+
+ // Mute
+ if ( !bFakeClient && GetClientVoiceMgr() )
+ {
+ const char *pszString = GetClientVoiceMgr()->IsPlayerBlocked( playerIndex ) ? "#TF_ScoreBoard_Context_UnMute" : "#TF_ScoreBoard_Context_Mute";
+ contextMenuBuilder.AddMenuItem( pszString, "muteplayer", "mute" );
+ }
+
+ // Spectate
+ if ( g_TF_PR->IsAlive( playerIndex ) && ( !pLocalTFPlayer->IsAlive() || pLocalTFPlayer->GetTeamNumber() == TEAM_SPECTATOR ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Spec", "specplayer", "spectate" );
+ }
+
+ // Add Friend
+ if ( !bFakeClient )
+ {
+ CSteamID steamID;
+ C_TFPlayer *pTFTarget = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );
+ if ( pTFTarget && pTFTarget->GetSteamID( &steamID ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamFriends()->GetFriendRelationship( steamID ) == k_EFriendRelationshipNone )
+ {
+ contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Friend", "friendadd", "friend" );
+ }
+ }
+ }
+
+ // Trade
+ if ( !bFakeClient && !bTournamentGame )
+ {
+ contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Trade", "jointrade", "trade" );
+ }
+
+ // Profile
+ if ( !bFakeClient )
+ {
+ contextMenuBuilder.AddMenuItem( "#TF_ScoreBoard_Context_Profile", "profile", "profile" );
+ }
+
+ int x, y;
+ vgui::input()->GetCursorPosition( x, y );
+ m_pRightClickMenu->SetPos( x, y );
+ m_pRightClickMenu->SetVisible( true );
+ m_pRightClickMenu->AddActionSignalTarget( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFClientScoreBoardDialog::UseMouseMode( void )
+{
+ return tf_scoreboard_mouse_mode.GetInt() == 1 || ( tf_scoreboard_mouse_mode.GetInt() == 2 && m_bMouseActivated );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::InitializeInputScheme( void )
+{
+ if ( !IsVisible() )
+ return;
+
+ if ( !m_pPlayerListBlue || !m_pPlayerListRed )
+ return;
+
+ MakePopup( false, UseMouseMode() );
+ SetKeyBoardInputEnabled( false );
+ SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListBlue->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListBlue->AddActionSignalTarget( this );
+ m_pPlayerListBlue->SetClickable( UseMouseMode() );
+ m_pPlayerListRed->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListRed->AddActionSignalTarget( this );
+ m_pPlayerListRed->SetClickable( UseMouseMode() );
+
+ if ( m_pPlayerListBlue->GetScrollBar() &&
+ m_pPlayerListBlue->GetScrollBar()->GetButton( 0 ) &&
+ m_pPlayerListBlue->GetScrollBar()->GetButton( 1 ) &&
+ m_pPlayerListBlue->GetScrollBar()->GetSlider() )
+ {
+ m_pPlayerListBlue->SetVerticalScrollbar( UseMouseMode() );
+ m_pPlayerListBlue->GetScrollBar()->GetButton( 0 )->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListBlue->GetScrollBar()->GetButton( 1 )->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListBlue->GetScrollBar()->GetSlider()->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListBlue->GetScrollBar()->SetMouseInputEnabled( UseMouseMode() );
+ }
+
+ if ( m_pPlayerListRed->GetScrollBar() &&
+ m_pPlayerListRed->GetScrollBar()->GetButton( 0 ) &&
+ m_pPlayerListRed->GetScrollBar()->GetButton( 1 ) &&
+ m_pPlayerListRed->GetScrollBar()->GetSlider() )
+ {
+ m_pPlayerListRed->SetVerticalScrollbar( UseMouseMode() );
+ m_pPlayerListRed->GetScrollBar()->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListRed->GetScrollBar()->GetButton( 0 )->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListRed->GetScrollBar()->GetButton( 1 )->SetMouseInputEnabled( UseMouseMode() );
+ m_pPlayerListRed->GetScrollBar()->GetSlider()->SetMouseInputEnabled( UseMouseMode() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets the scoreboard panel
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::Reset()
+{
+ if ( !m_bIsPVEMode )
+ {
+ InitPlayerList( m_pPlayerListBlue );
+ InitPlayerList( m_pPlayerListRed );
+ }
+
+ m_pPlayerListBlue->SetVisible( !m_bIsPVEMode );
+ m_pPlayerListRed->SetVisible( !m_bIsPVEMode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inits the player list in a list panel
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::InitPlayerList( SectionedListPanel *pPlayerList )
+{
+ pPlayerList->SetVerticalScrollbar( UseMouseMode() );
+ pPlayerList->RemoveAll();
+ pPlayerList->RemoveAllSections();
+ pPlayerList->AddSection( 0, "Players", TFPlayerSortFunc );
+ pPlayerList->SetSectionAlwaysVisible( 0, true );
+ pPlayerList->SetSectionFgColor( 0, Color( 255, 255, 255, 255 ) );
+ pPlayerList->SetBgColor( Color( 0, 0, 0, 0 ) );
+ pPlayerList->SetBorder( NULL );
+
+ pPlayerList->AddColumnToSection( 0, "medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iMedalWidth );
+
+ // Avatars are always displayed at 32x32 regardless of resolution
+ if ( ShowAvatars() )
+ {
+ pPlayerList->AddColumnToSection( 0, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iAvatarWidth );
+ pPlayerList->AddColumnToSection( 0, "spacer", "", 0, m_iSpacerWidth );
+ }
+
+ // the player avatar is always a fixed size, so as we change resolutions we need to vary the size of the name column to adjust the total width of all the columns
+ m_nExtraSpace = pPlayerList->GetWide() - m_iMedalWidth - m_iAvatarWidth - m_iSpacerWidth - m_iNameWidth - m_iKillstreakWidth - m_iKillstreakImageWidth - m_iNemesisWidth - m_iNemesisWidth - m_iScoreWidth - m_iClassWidth - m_iPingWidth - m_iSpacerWidth - ( 2 * SectionedListPanel::COLUMN_DATA_INDENT ); // the SectionedListPanel will indent the columns on either end by SectionedListPanel::COLUMN_DATA_INDENT
+
+ pPlayerList->AddColumnToSection( 0, "name", "#TF_Scoreboard_Name", 0, m_iNameWidth + m_nExtraSpace );
+ pPlayerList->AddColumnToSection( 0, "killstreak", "", SectionedListPanel::COLUMN_RIGHT, m_iKillstreakWidth );
+ pPlayerList->AddColumnToSection( 0, "killstreak_image", "", SectionedListPanel::COLUMN_IMAGE, m_iKillstreakImageWidth );
+ pPlayerList->AddColumnToSection( 0, "dominating", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iNemesisWidth );
+ pPlayerList->AddColumnToSection( 0, "nemesis", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, m_iNemesisWidth );
+ pPlayerList->AddColumnToSection( 0, "score", "#TF_Scoreboard_Score", SectionedListPanel::COLUMN_RIGHT, m_iScoreWidth );
+ pPlayerList->AddColumnToSection( 0, "class", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iClassWidth );
+
+ if ( tf_scoreboard_ping_as_text.GetBool() )
+ {
+ pPlayerList->AddColumnToSection( 0, "ping", "#TF_Scoreboard_Ping", SectionedListPanel::COLUMN_RIGHT, m_iPingWidth );
+ }
+ else
+ {
+ pPlayerList->AddColumnToSection( 0, "ping", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iPingWidth );
+ }
+#ifdef STAGING_ONLY
+// if ( m_bDisplayLevel )
+// {
+// pPlayerList->AddColumnToSection( 0, "level", "#TF_ScoreBoard_LevelLabel", SectionedListPanel::COLUMN_RIGHT, m_iPingWidth );
+// }
+#endif // STAGING_ONLY
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds the image list to use in the player list
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::SetPlayerListImages( vgui::SectionedListPanel *pPlayerList )
+{
+ pPlayerList->SetImageList( m_pImageList, false );
+ pPlayerList->SetVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the dialog
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::Update()
+{
+ // MvM
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ m_pMvMScoreboard->OnTick();
+ }
+
+ UpdateTeamInfo();
+ UpdatePlayerList();
+ UpdateSpectatorList();
+ UpdateArenaWaitingToPlayList();
+ UpdatePlayerDetails();
+ MoveToCenterOfScreen();
+ UpdateServerTimeLeft();
+ AdjustForVisibleScrollbar();
+ UpdateBadgePanels( m_pRedBadgePanels, m_pPlayerListRed );
+ UpdateBadgePanels( m_pBlueBadgePanels, m_pPlayerListBlue );
+
+ float flNextUpdate = 1.0f;
+ if ( UseMouseMode() )
+ {
+ if ( ( m_pPlayerListRed && m_pPlayerListRed->GetScrollBar() && m_pPlayerListRed->GetScrollBar()->IsVisible() ) ||
+ ( m_pPlayerListBlue && m_pPlayerListBlue->GetScrollBar() && m_pPlayerListBlue->GetScrollBar()->IsVisible() ) )
+ {
+ // we need a much faster update rate for MouseMode() so the MedalList can update when the scrollbar is quickly moved
+ // we're listening for the ScrollBarSliderMoved message but it still doesn't keep up with dragging the mouse around
+ flNextUpdate = 0.02f;
+ }
+ else
+ {
+ flNextUpdate = 0.15f;
+ }
+ }
+
+ m_fNextUpdateTime = gpGlobals->curtime + flNextUpdate;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates information about teams
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdateTeamInfo()
+{
+ // update the team sections in the scoreboard
+ for ( int teamIndex = TF_TEAM_RED; teamIndex <= TF_TEAM_BLUE; teamIndex++ )
+ {
+ C_TFTeam *team = GetGlobalTFTeam( teamIndex );
+ if ( team )
+ {
+ // choose dialog variables to set depending on team
+ const char *pDialogVarTeamScore = "";
+ const char *pDialogVarTeamPlayerCount = "";
+ const char *pDialogVarTeamName = "";
+
+ switch ( teamIndex )
+ {
+ case TF_TEAM_RED:
+ pDialogVarTeamScore = "redteamscore";
+ pDialogVarTeamPlayerCount = "redteamplayercount";
+ pDialogVarTeamName = "redteamname";
+ break;
+ case TF_TEAM_BLUE:
+ pDialogVarTeamScore = "blueteamscore";
+ pDialogVarTeamPlayerCount = "blueteamplayercount";
+ pDialogVarTeamName = "blueteamname";
+ break;
+ default:
+ Assert( false );
+ break;
+ }
+
+ // update # of players on each team
+ wchar_t string1[1024];
+ wchar_t wNumPlayers[6];
+ _snwprintf( wNumPlayers, ARRAYSIZE( wNumPlayers ), L"%i", team->Get_Number_Players() );
+ if ( team->Get_Number_Players() == 1 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( string1, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Player" ), 1, wNumPlayers );
+ }
+ else
+ {
+ g_pVGuiLocalize->ConstructString_safe( string1, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Players" ), 1, wNumPlayers );
+ }
+
+ // set # of players for team in dialog
+ SetDialogVariable( pDialogVarTeamPlayerCount, string1 );
+
+ // set team score in dialog
+ SetDialogVariable( pDialogVarTeamScore, team->Get_Score() );
+
+ // set the team name
+ SetDialogVariable( pDialogVarTeamName, team->Get_Localized_Name() );
+ }
+ }
+
+ bool bMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode();
+ bool bTournament = mp_tournament.GetBool() && !bMvM;
+ if ( m_pRedTeamName->IsVisible() != bTournament )
+ {
+ m_pRedTeamName->SetVisible( bTournament );
+ }
+
+ if ( m_pBlueTeamName->IsVisible() != bTournament )
+ {
+ m_pBlueTeamName->SetVisible( bTournament );
+ }
+
+ bool bShowAvatars = g_TF_PR && g_TF_PR->HasPremadeParties();
+ if ( bShowAvatars )
+ {
+ m_pRedLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderRedTeamIndex() ), k_EAvatarSize64x64 );
+ m_pRedLeaderAvatarImage->SetShouldDrawFriendIcon( false );
+ m_pBlueLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderBlueTeamIndex() ), k_EAvatarSize64x64 );
+ m_pBlueLeaderAvatarImage->SetShouldDrawFriendIcon( false );
+ }
+
+ m_pRedLeaderAvatarImage->SetVisible( bShowAvatars );
+ m_pRedLeaderAvatarBG->SetVisible( bShowAvatars );
+ m_pRedTeamImage->SetVisible( !bShowAvatars && !bMvM );
+
+ m_pBlueLeaderAvatarImage->SetVisible( bShowAvatars );
+ m_pBlueLeaderAvatarBG->SetVisible( bShowAvatars );
+ m_pBlueTeamImage->SetVisible( !bShowAvatars && !bMvM );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool AreEnemyTeams( int iTeam1, int iTeam2 )
+{
+ if ( iTeam1 == TF_TEAM_RED && iTeam2 == TF_TEAM_BLUE )
+ return true;
+
+ if ( iTeam1 == TF_TEAM_BLUE && iTeam2 == TF_TEAM_RED )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::AdjustForVisibleScrollbar( void )
+{
+ if ( m_pPlayerListRed && m_pPlayerListRed->GetScrollBar() && ( m_bRedScrollBarVisible != m_pPlayerListRed->GetScrollBar()->IsVisible() ) )
+ {
+ m_bRedScrollBarVisible = m_pPlayerListRed->GetScrollBar()->IsVisible();
+ int iScrollBarWidth = m_bRedScrollBarVisible ? m_pPlayerListRed->GetScrollBar()->GetWide() : 0;
+ m_pPlayerListRed->SetColumnWidthBySection( 0, "name", m_iNameWidth + m_nExtraSpace - iScrollBarWidth );
+ }
+
+ if ( m_pPlayerListBlue && m_pPlayerListBlue->GetScrollBar() && ( m_bBlueScrollBarVisible != m_pPlayerListBlue->GetScrollBar()->IsVisible() ) )
+ {
+ m_bBlueScrollBarVisible = m_pPlayerListBlue->GetScrollBar()->IsVisible();
+ int iScrollBarWidth = m_bBlueScrollBarVisible ? m_pPlayerListBlue->GetScrollBar()->GetWide() : 0;
+ m_pPlayerListBlue->SetColumnWidthBySection( 0, "name", m_iNameWidth + m_nExtraSpace - iScrollBarWidth );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, vgui::SectionedListPanel *pPlayerList )
+{
+ int iNumPanels = 0;
+
+ const IMatchGroupDescription *pMatchDesc = TFGameRules() ? GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ) : NULL;
+ const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL;
+ if ( pProgressionDesc && pPlayerList )
+ {
+ if ( TFGameRules()->IsMatchTypeCasual() )
+ {
+ int parentTall = pPlayerList->GetTall();
+ CTFBadgePanel *pPanel = NULL;
+
+ for ( int i = 0; i < pPlayerList->GetItemCount(); i++ )
+ {
+ KeyValues *pKeyValues = pPlayerList->GetItemData( i );
+ if ( !pKeyValues )
+ continue;
+
+ int iPlayerIndex = pKeyValues->GetInt( "playerIndex" );
+ const CSteamID steamID = GetSteamIDForPlayerIndex( iPlayerIndex );
+ if ( steamID.IsValid() )
+ {
+ if ( iNumPanels >= pBadgePanels.Count() )
+ {
+ pPanel = new CTFBadgePanel( this, "BadgePanel" );
+ pPanel->MakeReadyForUse();
+ pPanel->SetVisible( true );
+ pPanel->SetZPos( 9999 );
+ pBadgePanels.AddToTail( pPanel );
+ }
+ else
+ {
+ pPanel = pBadgePanels[iNumPanels];
+ }
+
+ int x, y, wide, tall;
+ pPlayerList->GetMaxCellBounds( i, 0, x, y, wide, tall );
+
+ if ( y + tall > parentTall )
+ continue;
+
+ if ( !pPanel->IsVisible() )
+ {
+ pPanel->SetVisible( true );
+ }
+
+ int xParent, yParent;
+ pPlayerList->GetPos( xParent, yParent );
+
+ int nPanelXPos, nPanelYPos, nPanelWide, nPanelTall;
+ pPanel->GetBounds( nPanelXPos, nPanelYPos, nPanelWide, nPanelTall );
+
+ if ( ( nPanelXPos != xParent + x )
+ || ( nPanelYPos != yParent + y )
+ || ( nPanelWide != wide )
+ || ( nPanelTall != tall ) )
+ {
+ pPanel->SetBounds( xParent + x, yParent + y, wide, tall );
+ pPanel->InvalidateLayout( true, true );
+ }
+
+ pPanel->SetupBadge( pProgressionDesc, steamID );
+ iNumPanels++;
+ }
+ }
+ }
+ }
+
+ // hide any unused images
+ for ( int i = iNumPanels; i < pBadgePanels.Count(); i++ )
+ {
+ if ( pBadgePanels[i]->IsVisible() )
+ {
+ pBadgePanels[i]->SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the player list
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdatePlayerList()
+{
+ int iSelectedPlayerIndex = GetLocalPlayerIndex();
+
+ // Save off which player we had selected
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ int itemID = pList->GetSelectedItem();
+
+ if ( itemID >= 0 )
+ {
+ KeyValues *pInfo = pList->GetItemData( itemID );
+ if ( pInfo )
+ {
+ iSelectedPlayerIndex = pInfo->GetInt( "playerIndex" );
+ }
+ }
+ }
+
+ m_pPlayerListRed->ClearSelection();
+ m_pPlayerListBlue->ClearSelection();
+ m_pPlayerListRed->RemoveAll();
+ m_pPlayerListBlue->RemoveAll();
+
+ if ( !g_TF_PR )
+ return;
+
+ C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalTFPlayer )
+ return;
+
+ int localteam = pLocalTFPlayer->GetTeamNumber();
+ if ( pLocalTFPlayer->m_bIsCoaching && pLocalTFPlayer->m_hStudent )
+ {
+ localteam = pLocalTFPlayer->m_hStudent->GetTeamNumber();
+ }
+
+ bool bMadeSelection = false;
+
+ for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
+ {
+ if ( g_PR->IsConnected( playerIndex ) || g_PR->IsValid( playerIndex ) )
+ {
+ SectionedListPanel *pPlayerList = NULL;
+ int nTeam = g_PR->GetTeam( playerIndex );
+ switch ( nTeam )
+ {
+ case TF_TEAM_BLUE:
+ pPlayerList = m_pPlayerListBlue;
+ break;
+ case TF_TEAM_RED:
+ pPlayerList = m_pPlayerListRed;
+ break;
+ }
+ if ( !pPlayerList )
+ continue;
+
+ MM_PlayerConnectionState_t eConnectionState = g_TF_PR->GetPlayerConnectionState( playerIndex );
+ const wchar_t *pwszFormat = NULL;
+ if ( eConnectionState == MM_DISCONNECTED )
+ {
+ pwszFormat = g_pVGuiLocalize->Find( "#TF_MM_PlayerLostConnection" );
+ }
+ else if ( ( eConnectionState == MM_CONNECTING ) || ( eConnectionState == MM_LOADING ) )
+ {
+ pwszFormat = g_pVGuiLocalize->Find( "#TF_MM_PlayerConnecting" );
+ }
+
+ if ( pwszFormat )
+ {
+ KeyValues *pKV = new KeyValues( "data" );
+ pKV->SetInt( "playerIndex", playerIndex );
+ pKV->SetInt( "connected", 1 );
+
+ // HOLY CHEESEBALL BUSY INDICATOR
+ const wchar_t *pwszEllipses = &L"....."[4 - ( (unsigned)Plat_FloatTime() % 5U )];
+ wchar_t wszLocalized[512];
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, pwszFormat, 1, pwszEllipses );
+ pKV->SetWString( "name", wszLocalized );
+
+ int itemID = pPlayerList->AddItem( 0, pKV );
+ pPlayerList->SetItemFgColor( itemID, ( eConnectionState == MM_DISCONNECTED ) ? Color( 208, 147, 7, 255 ) : Color( 76, 107, 34, 255 ) );
+ pPlayerList->SetItemBgColor( itemID, Color( 0, 0, 0, 80 ) );
+ pPlayerList->SetItemFont( itemID, m_hScoreFontSmallest );
+
+ pKV->deleteThis();
+
+ continue;
+ }
+
+ int iActiveDominations = g_TF_PR->GetActiveDominations( playerIndex );
+
+ // Debug getting some bogus counts here.
+ if ( iActiveDominations < 0 )
+ {
+ Assert( iActiveDominations >= 0 );
+ iActiveDominations = 0;
+ }
+ else if ( iActiveDominations >= MAX_PLAYERS )
+ {
+ Assert( iActiveDominations < MAX_PLAYERS );
+ iActiveDominations = MAX_PLAYERS - 1;
+ }
+
+ // Now limit based on the number of images we have
+ if ( iActiveDominations >= SCOREBOARD_DOMINATION_ICONS )
+ {
+ iActiveDominations = SCOREBOARD_DOMINATION_ICONS - 1;
+ }
+
+ bool bDominating;
+ if ( iActiveDominations > 0 )
+ {
+ bDominating = true;
+ }
+ else
+ {
+ bDominating = false;
+ }
+
+ bool bAlive = g_TF_PR->IsAlive( playerIndex );
+
+ int iDominationIndex = ( bDominating ? ( m_iImageDom[iActiveDominations] ) : 0 );
+ if ( !bAlive )
+ {
+ iDominationIndex = ( bDominating ? ( m_iImageDomDead[iActiveDominations] ) : 0 );
+ }
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "playerIndex", playerIndex );
+ pKeyValues->SetString( "name", g_TF_PR->GetPlayerName( playerIndex ) );
+ pKeyValues->SetInt( "dominating", iDominationIndex );
+ pKeyValues->SetInt( "score", g_TF_PR->GetTotalScore( playerIndex ) );
+ pKeyValues->SetInt( "connected", 2 );
+
+ C_TFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );
+
+ if ( pTFPlayer && pTFPlayer->GetActiveTFWeapon() )
+ {
+ int nCount = g_TF_PR->GetStreak( playerIndex, CTFPlayerShared::kTFStreak_Kills );
+ if ( nCount )
+ {
+ pKeyValues->SetInt( "killstreak_image", bAlive ? m_iImageStreak : m_iImageStreakDead );
+ pKeyValues->SetInt( "killstreak", nCount );
+ }
+ else
+ {
+ pKeyValues->SetInt( "killstreak_image", 0 );
+ pKeyValues->SetString( "killstreak", "" );
+ }
+ }
+
+ // check for bots first, so malicious server operators can't fake a ping and stuff their server with bots that look like players
+ if ( g_PR->IsFakePlayer( playerIndex ) )
+ {
+ if ( tf_scoreboard_ping_as_text.GetBool() )
+ {
+ pKeyValues->SetString( "ping", "#TF_Scoreboard_Bot" );
+ }
+ else
+ {
+ int iIndex = ( nTeam == TF_TEAM_RED ) ? PING_BOT_RED : PING_BOT_BLUE;
+ pKeyValues->SetInt( "ping", bAlive ? m_iImagePing[iIndex] : m_iImagePingDead[iIndex] );
+ }
+ }
+ else
+ {
+ int nPing = g_PR->GetPing( playerIndex );
+
+ if ( nPing < 1 )
+ {
+ if ( tf_scoreboard_ping_as_text.GetBool() )
+ {
+ pKeyValues->SetString( "ping", "" );
+ }
+ else
+ {
+ pKeyValues->SetInt( "ping", 0 );
+ }
+ }
+ else
+ {
+ if ( tf_scoreboard_ping_as_text.GetBool() )
+ {
+ pKeyValues->SetInt( "ping", nPing );
+ }
+ else
+ {
+ int iIndex = PING_VERY_HIGH;
+
+ if ( nPing < 125 )
+ {
+ iIndex = PING_LOW;
+ }
+ else if ( nPing < 200 )
+ {
+ iIndex = PING_MED;
+ }
+ else if ( nPing < 275 )
+ {
+ iIndex = PING_HIGH;
+ }
+
+ pKeyValues->SetInt( "ping", bAlive ? m_iImagePing[iIndex] : m_iImagePingDead[iIndex] );
+ }
+ }
+ }
+
+ if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) && TFGameRules()->ArePlayersInHell() )
+ {
+ bAlive &= pTFPlayer && !pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE );
+ }
+
+ // are they a Spy that's feigning death? mark them as dead in the scoreboard...
+ if ( g_TF_PR->GetPlayerClass( playerIndex ) == TF_CLASS_SPY )
+ {
+ if ( pTFPlayer && pTFPlayer->m_Shared.InCond( TF_COND_FEIGN_DEATH ) )
+ {
+ bAlive = false;
+ }
+ }
+
+ // can only see class information if we're on the same team
+ if ( !AreEnemyTeams( g_PR->GetTeam( playerIndex ), localteam ) && !( localteam == TEAM_UNASSIGNED ) )
+ {
+ // class name
+ if ( g_PR->IsConnected( playerIndex ) )
+ {
+ int iClass = g_TF_PR->GetPlayerClass( playerIndex );
+ if ( GetLocalPlayerIndex() == playerIndex && !bAlive )
+ {
+ // If this is local player and he is dead, show desired class (which he will spawn as) rather than current class.
+ int iDesiredClass = pLocalTFPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ // use desired class unless it's random -- if random, his future class is not decided until moment of spawn
+ if ( TF_CLASS_RANDOM != iDesiredClass )
+ {
+ iClass = iDesiredClass;
+ }
+ }
+ else
+ {
+ // for non-local players, show the current class
+ iClass = g_TF_PR->GetPlayerClass( playerIndex );
+ }
+
+ if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS )
+ {
+ if ( bAlive )
+ {
+ pKeyValues->SetInt( "class", tf_scoreboard_alt_class_icons.GetBool() ? m_iImageClassAlt[iClass] : m_iImageClass[iClass] );
+ }
+ else
+ {
+ pKeyValues->SetInt( "class", tf_scoreboard_alt_class_icons.GetBool() ? m_iImageClassAlt[iClass + 9] : m_iImageClass[iClass + 9] ); // +9 is to jump ahead to the darker dead icons
+ }
+ }
+ else
+ {
+ pKeyValues->SetInt( "class", 0 );
+ }
+ }
+ }
+ else
+ {
+ if ( pTFPlayer && pTFPlayer->m_Shared.IsPlayerDominated( pLocalTFPlayer->entindex() ) )
+ {
+ // if local player is dominated by this player, show a nemesis icon
+ pKeyValues->SetInt( "nemesis", bAlive ? m_iImageNemesis : m_iImageNemesisDead );
+ }
+ else if ( pLocalTFPlayer->m_Shared.IsPlayerDominated( playerIndex ) )
+ {
+ // if this player is dominated by the local player, show the domination icon
+ pKeyValues->SetInt( "nemesis", bAlive ? m_iImageDominated : m_iImageDominatedDead );
+ }
+ }
+
+#ifdef STAGING_ONLY
+// if ( m_bDisplayLevel )
+// {
+// pKeyValues->SetInt( "level", g_TF_PR->GetPlayerLevel( playerIndex ) );
+// }
+#endif // STAGING_ONLY
+
+ UpdatePlayerAvatar( playerIndex, pKeyValues );
+
+ // the medal column is just a place holder for the images that are displayed later
+ pKeyValues->SetInt( "medal", 0 );
+
+ int itemID = pPlayerList->AddItem( 0, pKeyValues );
+ Color clr = g_PR->GetTeamColor( nTeam );
+
+ // change color based off alive or dead status. Also slightly different if its local player since the name is highlighted.
+ if ( !bAlive )
+ {
+ if ( nTeam == TF_TEAM_RED )
+ {
+ if ( GetLocalPlayerIndex() != playerIndex )
+ {
+ clr = Color( 135, 83, 83, 255 );
+ }
+ else
+ {
+ clr = Color( 182, 75, 75, 255 );
+ }
+ }
+ else
+ {
+ if ( GetLocalPlayerIndex() != playerIndex )
+ {
+ clr = Color( 81, 97, 129, 255 );
+ }
+ else
+ {
+ clr = Color( 123, 153, 187, 255 );
+ }
+ }
+ }
+
+ pPlayerList->SetItemFgColor( itemID, clr );
+ pPlayerList->SetItemBgColor( itemID, Color( 0, 0, 0, 80 ) );
+ pPlayerList->SetItemFont( itemID, m_hScoreFontDefault );
+
+ if ( iSelectedPlayerIndex == playerIndex )
+ {
+ bMadeSelection = true;
+ pPlayerList->SetSelectedItem( itemID );
+ }
+
+ pKeyValues->deleteThis();
+ }
+ else
+ {
+ MM_PlayerConnectionState_t eConnectionState = g_TF_PR->GetPlayerConnectionState( playerIndex );
+ if ( eConnectionState == MM_WAITING_FOR_PLAYER )
+ {
+ SectionedListPanel *pPlayerList = NULL;
+ int nTeam = g_PR->GetTeam( playerIndex );
+ switch ( nTeam )
+ {
+ case TF_TEAM_BLUE:
+ pPlayerList = m_pPlayerListBlue;
+ break;
+ case TF_TEAM_RED:
+ pPlayerList = m_pPlayerListRed;
+ break;
+ }
+ if ( pPlayerList )
+ {
+ const wchar_t *pwszFormat = g_pVGuiLocalize->Find( "#TF_MM_LookingForPlayer" );
+ if ( pwszFormat )
+ {
+ KeyValues *pKV = new KeyValues( "data" );
+ pKV->SetInt( "playerIndex", playerIndex );
+ pKV->SetInt( "connected", 0 );
+
+ // HOLY CHEESEBALL BUSY INDICATOR
+ const wchar_t *pwszEllipses = &L"....."[4 - ( (unsigned)Plat_FloatTime() % 5U )];
+ wchar_t wszLocalized[512];
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, pwszFormat, 1, pwszEllipses );
+ pKV->SetWString( "name", wszLocalized );
+
+ int itemID = pPlayerList->AddItem( 0, pKV );
+ pPlayerList->SetItemFgColor( itemID, Color( 120, 120, 120, 255 ) );
+ pPlayerList->SetItemBgColor( itemID, Color( 0, 0, 0, 80 ) );
+ pPlayerList->SetItemFont( itemID, m_hScoreFontSmallest );
+
+ pKV->deleteThis();
+ }
+ }
+ }
+ }
+ }
+
+ // If we're on spectator, find a default selection
+#ifdef _X360
+ if ( !bMadeSelection )
+ {
+ if ( m_pPlayerListBlue->GetItemCount() > 0 )
+ {
+ m_pPlayerListBlue->SetSelectedItem( 0 );
+ }
+ else if ( m_pPlayerListRed->GetItemCount() > 0 )
+ {
+ m_pPlayerListRed->SetSelectedItem( 0 );
+ }
+ }
+#endif
+
+ // force the lists to PerformLayout() now so we can update our medal images after we return
+ m_pPlayerListRed->InvalidateLayout( true );
+ m_pPlayerListBlue->InvalidateLayout( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the spectator list
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdateSpectatorList()
+{
+ char szCoachList[512] = "" ;
+ int nCoaches = 0;
+ char szSpectatorList[512] = "" ;
+ int nSpectators = 0;
+ for( int playerIndex = 1 ; playerIndex <= MAX_PLAYERS; playerIndex++ )
+ {
+ if ( ShouldShowAsSpectator( playerIndex ) )
+ {
+ C_TFPlayer *pPlayer = (C_TFPlayer*)UTIL_PlayerByIndex( playerIndex );
+ if ( pPlayer && pPlayer->m_bIsCoaching && pPlayer->m_hStudent )
+ {
+ if ( nCoaches > 0 )
+ {
+ Q_strncat( szCoachList, ", ", ARRAYSIZE( szCoachList ) );
+ }
+
+ Q_strncat( szCoachList, g_PR->GetPlayerName( playerIndex ), ARRAYSIZE( szCoachList ) );
+ nCoaches++;
+ }
+ else
+ {
+ if ( nSpectators > 0 )
+ {
+ Q_strncat( szSpectatorList, ", ", ARRAYSIZE( szSpectatorList ) );
+ }
+
+ Q_strncat( szSpectatorList, g_PR->GetPlayerName( playerIndex ), ARRAYSIZE( szSpectatorList ) );
+ nSpectators++;
+ }
+ }
+ }
+
+ wchar_t wzText[512] = L"";
+ if ( nCoaches > 0 )
+ {
+ const char *pchFormat = ( 1 == nCoaches ? "#ScoreBoard_Coach" : "#ScoreBoard_Coaches" );
+
+ wchar_t wzCount[16];
+ wchar_t wzList[1024];
+ _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%i", nCoaches );
+ g_pVGuiLocalize->ConvertANSIToUnicode( szCoachList, wzList, sizeof( wzList ) );
+ g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( pchFormat), 2, wzCount, wzList );
+ }
+ if ( nSpectators > 0 )
+ {
+ const char *pchFormat = ( 1 == nSpectators ? "#ScoreBoard_Spectator" : "#ScoreBoard_Spectators" );
+
+ wchar_t wzSpectatorText[512] = L"";
+ wchar_t wzSpectatorCount[16];
+ wchar_t wzSpectatorList[1024];
+ _snwprintf( wzSpectatorCount, ARRAYSIZE( wzSpectatorCount ), L"%i", nSpectators );
+ g_pVGuiLocalize->ConvertANSIToUnicode( szSpectatorList, wzSpectatorList, sizeof( wzSpectatorList ) );
+ g_pVGuiLocalize->ConstructString_safe( wzSpectatorText, g_pVGuiLocalize->Find( pchFormat), 2, wzSpectatorCount, wzSpectatorList );
+ if ( nCoaches > 0 )
+ {
+ V_wcscat_safe( wzText, L". " );
+ }
+ V_wcscat_safe( wzText, wzSpectatorText );
+ }
+
+ SetDialogVariable( "spectators", wzText );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether the specified player index is an arena spectator
+//-----------------------------------------------------------------------------
+bool CTFClientScoreBoardDialog::ShouldShowAsArenaWaitingToPlay( int iPlayerIndex )
+{
+ if ( !g_TF_PR )
+ return false;
+
+ // see if player is connected
+ if ( g_TF_PR->IsConnected( iPlayerIndex ) )
+ {
+ if ( g_TF_PR->IsArenaSpectator( iPlayerIndex ) == true )
+ return false;
+
+ if ( g_TF_PR->GetPlayerClass( iPlayerIndex ) == TF_CLASS_UNDEFINED )
+ return false;
+
+ if ( g_TF_PR->GetTeam( iPlayerIndex ) > LAST_SHARED_TEAM )
+ return false;
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the spectator list
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdateArenaWaitingToPlayList()
+{
+ char szSpectatorList[512] = "" ;
+ wchar_t wzSpectators[512] = L"";
+
+ if ( TFGameRules() && TFGameRules()->IsInArenaMode() == true )
+ {
+ int nSpectators = 0;
+ for( int playerIndex = 1 ; playerIndex <= MAX_PLAYERS; playerIndex++ )
+ {
+ if ( ShouldShowAsArenaWaitingToPlay( playerIndex ) )
+ {
+ if ( nSpectators > 0 )
+ {
+ Q_strncat( szSpectatorList, ", ", ARRAYSIZE( szSpectatorList ) );
+ }
+
+ Q_strncat( szSpectatorList, g_PR->GetPlayerName( playerIndex ), ARRAYSIZE( szSpectatorList ) );
+ nSpectators++;
+ }
+ }
+
+ if ( nSpectators > 0 )
+ {
+ const char *pchFormat = ( 1 == nSpectators ? "#TF_Arena_ScoreBoard_Spectator" : "#TF_Arena_ScoreBoard_Spectators" );
+
+ wchar_t wzSpectatorCount[16];
+ wchar_t wzSpectatorList[1024];
+ _snwprintf( wzSpectatorCount, ARRAYSIZE( wzSpectatorCount ), L"%i", nSpectators );
+ g_pVGuiLocalize->ConvertANSIToUnicode( szSpectatorList, wzSpectatorList, sizeof( wzSpectatorList ) );
+ g_pVGuiLocalize->ConstructString_safe( wzSpectators, g_pVGuiLocalize->Find( pchFormat), 2, wzSpectatorCount, wzSpectatorList );
+ }
+ }
+
+ SetDialogVariable( "waitingtoplay", wzSpectators );
+}
+
+static void PopulateDuelPanel( CTFClientScoreBoardDialog::duel_panel_t &duelPanel, C_TFPlayer *pPlayer, int unScore )
+{
+ CSteamID steamID;
+ if ( duelPanel.m_pAvatar && pPlayer->GetSteamID( &steamID ) )
+ {
+ duelPanel.m_pAvatar->SetVisible( true );
+ duelPanel.m_pAvatar->SetShouldDrawFriendIcon( false );
+ duelPanel.m_pAvatar->SetPlayer( steamID, k_EAvatarSize64x64 );
+ }
+
+ if ( duelPanel.m_pPlayerNameLabel && g_TF_PR != NULL )
+ {
+ duelPanel.m_pPanel->SetDialogVariable( "playername", g_TF_PR->GetPlayerName( pPlayer->entindex() ) );
+ Color clr = g_PR->GetTeamColor( g_PR->GetTeam( pPlayer->entindex() ) );
+ duelPanel.m_pPlayerNameLabel->SetFgColor( clr );
+ }
+ duelPanel.m_pPanel->SetDialogVariable( "score", unScore );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates details about a player
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdatePlayerDetails()
+{
+ ClearPlayerDetails();
+
+ // by default, hide these, especially for source tv
+ m_pLocalPlayerStatsPanel->SetVisible( false );
+ m_pLocalPlayerDuelStatsPanel->SetVisible( false );
+
+ if ( !g_TF_PR )
+ return;
+
+ // Default is local player
+ C_TFPlayer *pSelectedPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pSelectedPlayer )
+ return;
+
+ int playerIndex = pSelectedPlayer->entindex();
+
+ // Change to selected player when using mouse mode
+ if ( UseMouseMode() )
+ {
+ SectionedListPanel *pList = GetSelectedPlayerList();
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pIssueKeyValues = pList->GetItemData( iSelectedItem );
+ if ( pIssueKeyValues )
+ {
+ playerIndex = pIssueKeyValues->GetInt( "playerIndex", 0 );
+ pSelectedPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );
+ if ( !pSelectedPlayer || !playerIndex )
+ return;
+ }
+ }
+ }
+ }
+
+ // Make sure the selected player is still connected.
+ if ( !g_TF_PR->IsConnected( playerIndex ) )
+ return;
+
+#if defined( REPLAY_ENABLED )
+ if ( engine->IsHLTV() || g_pEngineClientReplay->IsPlayingReplayDemo() )
+#else
+ if ( engine->IsHLTV() )
+#endif
+ {
+ SetDialogVariable( "playername", g_TF_PR->GetPlayerName( playerIndex ) );
+ return;
+ }
+
+ uint32 unMyScore, unOpponentScore;
+ C_TFPlayer *pDuelingPartner = NULL;
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pSelectedPlayer == pLocalPlayer && DuelMiniGame_GetStats( &pDuelingPartner, unMyScore, unOpponentScore ) )
+ {
+ PopulateDuelPanel( m_duelPanelLocalPlayer, pLocalPlayer, unMyScore );
+ PopulateDuelPanel( m_duelPanelOpponent, pDuelingPartner, unOpponentScore );
+
+ if ( m_pLocalPlayerStatsPanel->IsVisible() == true )
+ {
+ m_pLocalPlayerStatsPanel->SetVisible( false );
+ }
+ if ( m_pLocalPlayerDuelStatsPanel->IsVisible() == false )
+ {
+ m_pLocalPlayerDuelStatsPanel->SetVisible( true );
+ }
+ }
+ else
+ {
+ if ( m_pLocalPlayerStatsPanel->IsVisible() == false )
+ {
+ m_pLocalPlayerStatsPanel->SetVisible( true );
+ }
+ if ( m_pLocalPlayerDuelStatsPanel->IsVisible() == true )
+ {
+ m_pLocalPlayerDuelStatsPanel->SetVisible( false );
+ }
+
+ Color cGreen = Color( 0, 255, 0, 255 );
+ Color cWhite = Color( 255, 255, 255, 255 );
+
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "kills", g_TF_PR->GetPlayerScore( playerIndex ) );
+ if ( m_pKillsLabel )
+ {
+ m_pKillsLabel->SetFgColor( g_TF_PR->GetPlayerScore( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "deaths", g_TF_PR->GetDeaths( playerIndex ) );
+ if ( m_pDeathsLabel )
+ {
+ m_pDeathsLabel->SetFgColor( g_TF_PR->GetDeaths( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "assists", pSelectedPlayer->m_Shared.GetKillAssists( playerIndex ) );
+ if ( m_pAssistLabel )
+ {
+ m_pAssistLabel->SetFgColor( pSelectedPlayer->m_Shared.GetKillAssists( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "destruction", pSelectedPlayer->m_Shared.GetBuildingsDestroyed( playerIndex ) );
+ if ( m_pDestructionLabel )
+ {
+ m_pDestructionLabel->SetFgColor( pSelectedPlayer->m_Shared.GetBuildingsDestroyed( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "captures", pSelectedPlayer->m_Shared.GetCaptures( playerIndex ) );
+ if ( m_pCapturesLabel )
+ {
+ m_pCapturesLabel->SetFgColor( pSelectedPlayer->m_Shared.GetCaptures( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "defenses", pSelectedPlayer->m_Shared.GetDefenses( playerIndex ) );
+ if ( m_pDefensesLabel )
+ {
+ m_pDefensesLabel->SetFgColor( pSelectedPlayer->m_Shared.GetDefenses( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "dominations", pSelectedPlayer->m_Shared.GetDominations( playerIndex ) );
+ if ( m_pDominationsLabel )
+ {
+ m_pDominationsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetDominations( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "revenge", pSelectedPlayer->m_Shared.GetRevenge( playerIndex ) );
+ if ( m_pRevengeLabel )
+ {
+ m_pRevengeLabel->SetFgColor( pSelectedPlayer->m_Shared.GetRevenge( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "healing", pSelectedPlayer->m_Shared.GetHealPoints( playerIndex ) );
+ if ( m_pHealingLabel )
+ {
+ m_pHealingLabel->SetFgColor( pSelectedPlayer->m_Shared.GetHealPoints( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "invulns", pSelectedPlayer->m_Shared.GetInvulns( playerIndex ) );
+ if ( m_pInvulnsLabel )
+ {
+ m_pInvulnsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetInvulns( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "teleports", pSelectedPlayer->m_Shared.GetTeleports( playerIndex ) );
+ if ( m_pTeleportsLabel )
+ {
+ m_pTeleportsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetTeleports( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "headshots", pSelectedPlayer->m_Shared.GetHeadshots( playerIndex ) );
+ if ( m_pHeadshotsLabel )
+ {
+ m_pHeadshotsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetHeadshots( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "backstabs", pSelectedPlayer->m_Shared.GetBackstabs( playerIndex ) );
+ if ( m_pBackstabsLabel )
+ {
+ m_pBackstabsLabel->SetFgColor( pSelectedPlayer->m_Shared.GetBackstabs( playerIndex ) ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "bonus", pSelectedPlayer->m_Shared.GetBonusPoints( playerIndex ) );
+ if ( m_pBonusLabel )
+ {
+ m_pBonusLabel->SetFgColor( pSelectedPlayer->m_Shared.GetBonusPoints( playerIndex ) ? cGreen : cWhite );
+ }
+
+ int nSupport = TFGameRules() ? TFGameRules()->CalcPlayerSupportScore( NULL, playerIndex ) : 0;
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "support", nSupport );
+ if ( m_pSupportLabel )
+ {
+ m_pSupportLabel->SetFgColor( nSupport ? cGreen : cWhite );
+ }
+ m_pLocalPlayerStatsPanel->SetDialogVariable( "damage", g_TF_PR->GetDamage( playerIndex ) );
+ if ( m_pDamageLabel )
+ {
+ m_pDamageLabel->SetFgColor( g_TF_PR->GetDamage( playerIndex ) ? cGreen : cWhite );
+ }
+ }
+
+ SetDialogVariable( "playername", g_TF_PR->GetPlayerName( playerIndex ) );
+
+ Color clr = g_PR->GetTeamColor( g_PR->GetTeam( playerIndex ) );
+ m_pLabelPlayerName->SetFgColor( clr );
+ m_pImagePanelHorizLine->SetFillColor( clr );
+
+ // update our image if our selected player or mode of display has changed
+ if ( ( m_hSelectedPlayer != pSelectedPlayer ) || ( m_bUsePlayerModel != cl_hud_playerclass_use_playermodel.GetBool() ) )
+ {
+ m_hSelectedPlayer = pSelectedPlayer;
+ m_bUsePlayerModel = cl_hud_playerclass_use_playermodel.GetBool();
+
+ int iClass = pSelectedPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ int iTeam = pSelectedPlayer->GetTeamNumber();
+ if ( ( pLocalPlayer->InSameTeam( pSelectedPlayer ) || pLocalPlayer->GetTeamNumber() < FIRST_GAME_TEAM ) &&
+ iTeam >= FIRST_GAME_TEAM && iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS )
+ {
+ if ( cl_hud_playerclass_use_playermodel.GetBool() )
+ {
+ if ( !m_pPlayerModelPanel->IsVisible() )
+ {
+ m_pPlayerModelPanel->SetVisible( true );
+ }
+
+ if ( m_pClassImage->IsVisible() )
+ {
+ m_pClassImage->SetVisible( false );
+ }
+
+ m_nPlayerModelPanelIndex = pSelectedPlayer->entindex();
+ UpdatePlayerModel();
+ }
+ else
+ {
+ if ( m_pPlayerModelPanel->IsVisible() )
+ {
+ m_pPlayerModelPanel->SetVisible( false );
+ }
+
+ if ( !m_pClassImage->IsVisible() )
+ {
+ m_pClassImage->SetVisible( true );
+ }
+
+ m_pClassImage->SetClass( iTeam, iClass, 0 );
+ }
+ }
+ else
+ {
+ if ( m_pPlayerModelPanel->IsVisible() )
+ {
+ m_pPlayerModelPanel->SetVisible( false );
+ }
+ if ( m_pClassImage->IsVisible() )
+ {
+ m_pClassImage->SetVisible( false );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates Server Time Left
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::UpdateServerTimeLeft()
+{
+ wchar_t wzServerTimeHrsLeft[128];
+ wchar_t wzServerTimeMinLeft[128];
+ wchar_t wzServerTimeSecLeft[128];
+ wchar_t wzServerTimeLeft[128];
+
+ int iTimeLeft = 0;
+ int iHours = 0;
+ int iMinutes = 0;
+ int iSeconds = 0;
+ int iServerTimeLimit = mp_timelimit.GetInt() * 60;
+
+ if ( iServerTimeLimit == 0 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_NoTimeLimit" ), 0 );
+ SetDialogVariable( "servertimeleft", wzServerTimeLeft );
+
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_NoTimeLimitNew" ), 0 );
+ SetDialogVariable( "servertime", wzServerTimeLeft );
+
+ if ( m_pServerTimeLeftValue && m_pServerTimeLeftValue->IsVisible() && ( m_pFontTimeLeftString != vgui::INVALID_FONT ) )
+ {
+ m_pServerTimeLeftValue->SetFont( m_pFontTimeLeftString );
+ m_pServerTimeLeftValue->InvalidateLayout();
+ }
+
+ return;
+ }
+
+ iTimeLeft = TFGameRules() ? TFGameRules()->GetTimeLeft() : 0;
+
+ if ( iTimeLeft < 0 )
+ {
+ iTimeLeft = 0;
+ }
+ if ( iTimeLeft == 0 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_ChangeOnRoundEnd" ), 0 );
+ SetDialogVariable( "servertimeleft", wzServerTimeLeft );
+
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_ChangeOnRoundEndNew" ), 0 );
+ SetDialogVariable( "servertime", wzServerTimeLeft );
+
+ if ( m_pServerTimeLeftValue && m_pServerTimeLeftValue->IsVisible() && ( m_pFontTimeLeftString != vgui::INVALID_FONT ) )
+ {
+ m_pServerTimeLeftValue->SetFont( m_pFontTimeLeftString );
+ m_pServerTimeLeftValue->InvalidateLayout();
+ }
+
+ return;
+ }
+
+ iHours = iTimeLeft / 3600;
+ iMinutes = ( iTimeLeft % 3600 ) / 60;
+ iSeconds = ( iTimeLeft % 60 );
+
+ _snwprintf( wzServerTimeHrsLeft, ARRAYSIZE( wzServerTimeHrsLeft ), L"%i", iHours );
+ _snwprintf( wzServerTimeMinLeft, ARRAYSIZE( wzServerTimeMinLeft ), L"%02i", iMinutes );
+ _snwprintf( wzServerTimeSecLeft, ARRAYSIZE( wzServerTimeSecLeft ), L"%02i", iSeconds );
+
+ if ( m_pServerTimeLeftValue && m_pServerTimeLeftValue->IsVisible() && ( m_pFontTimeLeftNumbers != vgui::INVALID_FONT ) )
+ {
+ m_pServerTimeLeftValue->SetFont( m_pFontTimeLeftNumbers );
+ m_pServerTimeLeftValue->InvalidateLayout();
+ }
+
+ if ( iHours == 0 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeftNoHours" ), 2, wzServerTimeMinLeft, wzServerTimeSecLeft );
+ SetDialogVariable( "servertimeleft", wzServerTimeLeft );
+
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeftNoHoursNew" ), 2, wzServerTimeMinLeft, wzServerTimeSecLeft );
+ SetDialogVariable( "servertime", wzServerTimeLeft );
+
+ return;
+ }
+
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeft" ), 3, wzServerTimeHrsLeft, wzServerTimeMinLeft, wzServerTimeSecLeft );
+ SetDialogVariable( "servertimeleft", wzServerTimeLeft );
+
+ g_pVGuiLocalize->ConstructString_safe( wzServerTimeLeft, g_pVGuiLocalize->Find( "#Scoreboard_TimeLeftNew" ), 3, wzServerTimeHrsLeft, wzServerTimeMinLeft, wzServerTimeSecLeft );
+ SetDialogVariable( "servertime", wzServerTimeLeft );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clears score details
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::ClearPlayerDetails()
+{
+ // HLTV has no game stats
+#if defined( REPLAY_ENABLED )
+ bool bVisible = !engine->IsHLTV() && !g_pEngineClientReplay->IsPlayingReplayDemo();
+#else
+ bool bVisible = !engine->IsHLTV();
+#endif
+
+ SetDialogVariable( "kills", "" );
+ SetControlVisible( "KillsLabel", bVisible );
+
+ SetDialogVariable( "deaths", "" );
+ SetControlVisible( "DeathsLabel", bVisible );
+
+ SetDialogVariable( "captures", "" );
+ SetControlVisible( "CapturesLabel", bVisible );
+
+ SetDialogVariable( "defenses", "" );
+ SetControlVisible( "DefensesLabel", bVisible );
+
+ SetDialogVariable( "dominations", "" );
+ SetControlVisible( "DominationLabel", bVisible );
+
+ SetDialogVariable( "revenge", "" );
+ SetControlVisible( "RevengeLabel", bVisible );
+
+ SetDialogVariable( "assists", "" );
+ SetControlVisible( "AssistsLabel", bVisible );
+
+ SetDialogVariable( "destruction", "" );
+ SetControlVisible( "DestructionLabel", bVisible );
+
+ SetDialogVariable( "healing", "" );
+ SetControlVisible( "HealingLabel", bVisible );
+
+ SetDialogVariable( "invulns", "" );
+ SetControlVisible( "InvulnLabel", bVisible );
+
+ SetDialogVariable( "teleports", "" );
+ SetControlVisible( "TeleportsLabel", bVisible );
+
+ SetDialogVariable( "headshots", "" );
+ SetControlVisible( "HeadshotsLabel", bVisible );
+
+ SetDialogVariable( "backstabs", "" );
+ SetControlVisible( "BackstabsLabel", bVisible );
+
+ SetDialogVariable( "bonus", "" );
+ SetControlVisible( "BonusLabel", bVisible );
+
+ SetDialogVariable( "support", "" );
+ SetControlVisible( "SupportLabel", bVisible );
+
+ SetDialogVariable( "damage", "" );
+ SetControlVisible( "DamageLabel", bVisible );
+
+ SetDialogVariable( "playername", "" );
+
+// SetDialogVariable( "playerscore", "" );
+
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for sorting players
+//-----------------------------------------------------------------------------
+bool CTFClientScoreBoardDialog::TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 )
+{
+ KeyValues *it1 = list->GetItemData(itemID1);
+ KeyValues *it2 = list->GetItemData(itemID2);
+ Assert(it1 && it2);
+
+ // first compare score
+ int v1 = it1->GetInt("score");
+ int v2 = it2->GetInt("score");
+ if (v1 > v2)
+ return true;
+ else if (v1 < v2)
+ return false;
+
+ // sort by connected status next ( 0 is looking for player, 1 is disconnected/connecting/loading, 2 is fully connected )
+ int connected1 = it1->GetInt( "connected" );
+ int connected2 = it2->GetInt( "connected" );
+ if ( ( connected1 != 2 ) || ( connected2 != 2 ) )
+ return ( connected1 > connected2 );
+
+ // if score is the same, use player index to get deterministic sort
+ int iPlayerIndex1 = it1->GetInt( "playerIndex" );
+ int iPlayerIndex2 = it2->GetInt( "playerIndex" );
+ return ( iPlayerIndex1 > iPlayerIndex2 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a localized string of form "1 point", "2 points", etc for specified # of points
+//-----------------------------------------------------------------------------
+const wchar_t *GetPointsString( int iPoints )
+{
+ wchar_t wzScoreVal[128];
+ static wchar_t wzScore[128];
+ _snwprintf( wzScoreVal, ARRAYSIZE( wzScoreVal ), L"%i", iPoints );
+ if ( 1 == iPoints )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wzScore, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Point" ), 1, wzScoreVal );
+ }
+ else
+ {
+ g_pVGuiLocalize->ConstructString_safe( wzScore, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Points" ), 1, wzScoreVal );
+ }
+ return wzScore;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether the specified player index is a spectator
+//-----------------------------------------------------------------------------
+bool CTFClientScoreBoardDialog::ShouldShowAsSpectator( int iPlayerIndex )
+{
+ if ( !g_TF_PR )
+ return false;
+
+ // see if player is connected
+ if ( g_TF_PR->IsConnected( iPlayerIndex ) )
+ {
+ // either spectating or unassigned team should show in spectator list
+ int iTeam = g_TF_PR->GetTeam( iPlayerIndex );
+
+ if ( TFGameRules() )
+ {
+ if ( TFGameRules()->IsInArenaMode() )
+ {
+ if ( g_TF_PR->IsArenaSpectator( iPlayerIndex ) )
+ return true;
+
+ return false;
+ }
+ else if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( g_TF_PR->IsFakePlayer( iPlayerIndex ) )
+ return false;
+ }
+ }
+
+ if ( TEAM_SPECTATOR == iTeam || TEAM_UNASSIGNED == iTeam )
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Event handler
+//-----------------------------------------------------------------------------
+void CTFClientScoreBoardDialog::FireGameEvent( IGameEvent *event )
+{
+ const char *type = event->GetName();
+
+ if ( FStrEq( type, "server_spawn" ) )
+ {
+ // set server name in scoreboard
+ const char *hostname = event->GetString( "hostname" );
+ wchar_t wzHostName[256];
+ wchar_t wzServerLabel[256];
+ g_pVGuiLocalize->ConvertANSIToUnicode( hostname, wzHostName, sizeof( wzHostName ) );
+ g_pVGuiLocalize->ConstructString_safe( wzServerLabel, g_pVGuiLocalize->Find( "#Scoreboard_Server" ), 1, wzHostName );
+ SetDialogVariable( "server", wzServerLabel );
+ const char *pMapName = event->GetString( "mapname" );
+ SetDialogVariable( "mapname", GetMapDisplayName( pMapName ) );
+ // m_pLocalPlayerStatsPanel->SetDialogVariable( "gametype", g_pVGuiLocalize->Find( GetMapType( pMapName ) ) );
+ }
+#ifdef STAGING_ONLY
+// else if ( FStrEq( type, "bountymode_toggled" ) )
+// {
+// m_bDisplayLevel = event->GetBool( "active" );
+// InvalidateLayout( true, true );
+// }
+#endif
+
+ if ( IsVisible() )
+ {
+ Update();
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+SectionedListPanel *CTFClientScoreBoardDialog::GetSelectedPlayerList( void )
+{
+ SectionedListPanel *pList = NULL;
+
+ // navigation
+ if ( m_pPlayerListBlue->GetSelectedItem() >= 0 )
+ {
+ pList = m_pPlayerListBlue;
+ }
+ else if ( m_pPlayerListRed->GetSelectedItem() >= 0 )
+ {
+ pList = m_pPlayerListRed;
+ }
+
+ return pList;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Event handler
+//-----------------------------------------------------------------------------
+int CTFClientScoreBoardDialog::HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding )
+{
+ if ( !IsVisible() )
+ return 1;
+
+ if ( !down )
+ {
+ return 1;
+ }
+
+ if ( tf_scoreboard_mouse_mode.GetInt() == 2 )
+ {
+ if ( !m_bMouseActivated && ( keynum == MOUSE_LEFT || keynum == MOUSE_RIGHT ) )
+ {
+ m_bMouseActivated = true;
+ InitializeInputScheme();
+ return 0;
+ }
+ }
+
+#if defined ( _X360 )
+ SectionedListPanel *pList = GetSelectedPlayerList();
+
+ switch( keynum )
+ {
+ case KEY_XBUTTON_UP:
+ {
+ if ( pList )
+ {
+ pList->MoveSelectionUp();
+ }
+ }
+ return 0;
+
+ case KEY_XBUTTON_DOWN:
+ {
+ if ( pList )
+ {
+ pList->MoveSelectionDown();
+ }
+ }
+ return 0;
+
+ case KEY_XBUTTON_RIGHT:
+ {
+ if ( m_pPlayerListRed->GetItemCount() == 0 )
+ return 0;
+
+ // move to the red list
+
+ // get the row we're in now
+ int iSelectedBlueItem = m_pPlayerListBlue->GetSelectedItem();
+
+ m_pPlayerListBlue->ClearSelection();
+
+ if ( iSelectedBlueItem >= 0 )
+ {
+ int row = m_pPlayerListBlue->GetRowFromItemID( iSelectedBlueItem );
+
+ if ( row >= 0 )
+ {
+ int iNewItem = m_pPlayerListRed->GetItemIDFromRow( row );
+
+ if ( iNewItem >= 0 )
+ {
+ m_pPlayerListRed->SetSelectedItem( iNewItem );
+ }
+ else
+ {
+ // we have fewer items. Select the last one
+ int iLastRow = m_pPlayerListRed->GetItemCount()-1;
+
+ iNewItem = m_pPlayerListRed->GetItemIDFromRow( iLastRow );
+
+ if ( iNewItem >= 0 )
+ {
+ m_pPlayerListRed->SetSelectedItem( iNewItem );
+ }
+ }
+ }
+ }
+ }
+ return 0;
+
+ case KEY_XBUTTON_LEFT:
+ {
+ if ( m_pPlayerListBlue->GetItemCount() == 0 )
+ return 0;
+
+ // move to the blue list
+
+ // get the row we're in now
+ int iSelectedRedItem = m_pPlayerListRed->GetSelectedItem();
+
+ if ( iSelectedRedItem < 0 )
+ iSelectedRedItem = 0;
+
+ m_pPlayerListRed->ClearSelection();
+
+ if ( iSelectedRedItem >= 0 )
+ {
+ int row = m_pPlayerListRed->GetRowFromItemID( iSelectedRedItem );
+
+ if ( row >= 0 )
+ {
+ int iNewItem = m_pPlayerListBlue->GetItemIDFromRow( row );
+
+ if ( iNewItem >= 0 )
+ {
+ m_pPlayerListBlue->SetSelectedItem( iNewItem );
+ }
+ else
+ {
+ // we have fewer items. Select the last one
+ int iLastRow = m_pPlayerListBlue->GetItemCount()-1;
+
+ iNewItem = m_pPlayerListBlue->GetItemIDFromRow( iLastRow );
+
+ if ( iNewItem >= 0 )
+ {
+ m_pPlayerListBlue->SetSelectedItem( iNewItem );
+ }
+ }
+ }
+ }
+ }
+ return 0;
+
+ case KEY_XBUTTON_B:
+ {
+ ShowPanel( false );
+ }
+ return 0;
+
+ case KEY_XBUTTON_A: // Show GamerCard for the selected player
+ {
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pInfo = pList->GetItemData( iSelectedItem );
+
+ DevMsg( 1, "XShowGamerCardUI for player '%s'\n", pInfo->GetString( "name" ) );
+
+ uint64 xuid = matchmaking->PlayerIdToXuid( pInfo->GetInt( "playerIndex" ) );
+ XShowGamerCardUI( XBX_GetPrimaryUserId(), xuid );
+ }
+ }
+ }
+ return 0;
+
+ case KEY_XBUTTON_X: // Show player review for the selected player
+ {
+ if ( pList )
+ {
+ int iSelectedItem = pList->GetSelectedItem();
+
+ if ( iSelectedItem >= 0 )
+ {
+ KeyValues *pInfo = pList->GetItemData( iSelectedItem );
+
+ DevMsg( 1, "XShowPlayerReviewUI for player '%s'\n", pInfo->GetString( "name" ) );
+
+ uint64 xuid = matchmaking->PlayerIdToXuid( pInfo->GetInt( "playerIndex" ) );
+ XShowPlayerReviewUI( XBX_GetPrimaryUserId(), xuid );
+ }
+ }
+ }
+ return 0;
+
+ default:
+ break;
+ }
+#endif //_X360
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool ShouldScoreBoardHandleKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding )
+{
+ // We're only looking for specific mouse input
+ if ( keynum == MOUSE_LEFT || keynum == MOUSE_RIGHT )
+ {
+ CTFClientScoreBoardDialog *pScoreBoard = dynamic_cast< CTFClientScoreBoardDialog* >( gViewPortInterface->FindPanelByName( PANEL_SCOREBOARD ) );
+ if ( pScoreBoard )
+ {
+ return !pScoreBoard->HudElementKeyInput( down, keynum, pszCurrentBinding );
+ }
+ }
+
+ return false;
+}
diff --git a/game/client/tf/vgui/tf_clientscoreboard.h b/game/client/tf/vgui/tf_clientscoreboard.h
new file mode 100644
index 0000000..c297035
--- /dev/null
+++ b/game/client/tf/vgui/tf_clientscoreboard.h
@@ -0,0 +1,183 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_SCOREBOARD_H
+#define TF_SCOREBOARD_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "hud.h"
+#include "hudelement.h"
+#include "tf_hud_playerstatus.h"
+#include "clientscoreboarddialog.h"
+#include "tf_hud_mann_vs_machine_scoreboard.h"
+
+class CAvatarImagePanel;
+class CTFBadgePanel;
+//class CTFStatsGraph;
+
+//-----------------------------------------------------------------------------
+// Purpose: displays the scoreboard
+//-----------------------------------------------------------------------------
+
+class CTFClientScoreBoardDialog : public CClientScoreBoardDialog
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFClientScoreBoardDialog, CClientScoreBoardDialog );
+
+public:
+ CTFClientScoreBoardDialog( IViewPort *pViewPort );
+ virtual ~CTFClientScoreBoardDialog();
+
+ virtual void Reset() OVERRIDE;
+ virtual void Update() OVERRIDE;
+ virtual void ShowPanel( bool bShow ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding );
+
+ struct duel_panel_t
+ {
+ vgui::EditablePanel *m_pPanel;
+ CAvatarImagePanel *m_pAvatar;
+ CExLabel *m_pPlayerNameLabel;
+ };
+
+ MESSAGE_FUNC_PTR( OnItemSelected, "ItemSelected", panel );
+ MESSAGE_FUNC_PTR( OnItemContextMenu, "ItemContextMenu", panel );
+ void OnScoreBoardMouseRightRelease( void );
+
+ MESSAGE_FUNC_PARAMS( OnReportPlayer, "ReportPlayer", pData );
+
+protected:
+ virtual void PerformLayout();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ virtual void PostApplySchemeSettings( vgui::IScheme *pScheme ) {};
+
+ vgui::SectionedListPanel *GetPlayerListRed( void ){ return m_pPlayerListRed; }
+ vgui::SectionedListPanel *GetPlayerListBlue( void ){ return m_pPlayerListBlue; }
+
+private:
+ void InitPlayerList( vgui::SectionedListPanel *pPlayerList );
+ void SetPlayerListImages( vgui::SectionedListPanel *pPlayerList );
+ void UpdateTeamInfo();
+ void UpdatePlayerList();
+ void UpdateSpectatorList();
+ void UpdatePlayerDetails();
+ void UpdateServerTimeLeft();
+ void UpdateArenaWaitingToPlayList( void );
+ void ClearPlayerDetails();
+ bool ShouldShowAsSpectator( int iPlayerIndex );
+ bool ShouldShowAsArenaWaitingToPlay( int iPlayerIndex );
+ void GetCameraUnderlayBounds( int *pX, int *pY, int *pWide, int *pTall );
+ bool UseMouseMode( void );
+ void InitializeInputScheme( void );
+
+ void AdjustForVisibleScrollbar( void );
+ void UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, vgui::SectionedListPanel *pPlayerList );
+
+ virtual void FireGameEvent( IGameEvent *event );
+
+ static bool TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 );
+
+ vgui::SectionedListPanel *GetSelectedPlayerList( void );
+
+ void UpdatePlayerModel();
+
+ vgui::SectionedListPanel *m_pPlayerListBlue;
+ vgui::SectionedListPanel *m_pPlayerListRed;
+ CExLabel *m_pLabelPlayerName;
+ CExLabel *m_pLabelDuelOpponentPlayerName;
+ vgui::ImagePanel *m_pImagePanelHorizLine;
+ CTFClassImage *m_pClassImage;
+ vgui::EditablePanel *m_pLocalPlayerStatsPanel;
+ vgui::EditablePanel *m_pLocalPlayerDuelStatsPanel;
+ duel_panel_t m_duelPanelLocalPlayer;
+ duel_panel_t m_duelPanelOpponent;
+ vgui::Menu *m_pRightClickMenu;
+
+ CExLabel *m_pKillsLabel;
+ CExLabel *m_pDeathsLabel;
+ CExLabel *m_pAssistLabel;
+ CExLabel *m_pDestructionLabel;
+ CExLabel *m_pCapturesLabel;
+ CExLabel *m_pDefensesLabel;
+ CExLabel *m_pDominationsLabel;
+ CExLabel *m_pRevengeLabel;
+ CExLabel *m_pHealingLabel;
+ CExLabel *m_pInvulnsLabel;
+ CExLabel *m_pTeleportsLabel;
+ CExLabel *m_pHeadshotsLabel;
+ CExLabel *m_pBackstabsLabel;
+ CExLabel *m_pBonusLabel;
+ CExLabel *m_pSupportLabel;
+ CExLabel *m_pDamageLabel;
+
+ CExLabel *m_pServerTimeLeftValue;
+ vgui::HFont m_pFontTimeLeftNumbers;
+ vgui::HFont m_pFontTimeLeftString;
+
+ CTFHudMannVsMachineScoreboard *m_pMvMScoreboard;
+
+ int m_iImageDominated;
+ int m_iImageDominatedDead;
+ int m_iImageNemesis;
+ int m_iImageNemesisDead;
+ int m_iImageStreak;
+ int m_iImageStreakDead;
+
+ int m_iImageDom[SCOREBOARD_DOMINATION_ICONS];
+ int m_iImageDomDead[SCOREBOARD_DOMINATION_ICONS];
+ int m_iImageClass[SCOREBOARD_CLASS_ICONS];
+ int m_iImageClassAlt[SCOREBOARD_CLASS_ICONS];
+
+ int m_iImagePing[SCOREBOARD_PING_ICONS];
+ int m_iImagePingDead[SCOREBOARD_PING_ICONS];
+
+ int m_iTextureCamera;
+
+ bool m_bIsPVEMode;
+// bool m_bDisplayLevel;
+ bool m_bMouseActivated;
+ vgui::HFont m_hScoreFontDefault;
+ vgui::HFont m_hScoreFontSmallest;
+
+ CPanelAnimationVarAliasType( int, m_iSpacerWidth, "spacer", "5", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iNemesisWidth, "nemesis_width", "20", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iMedalWidth, "medal_width", "15", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iKillstreakWidth, "killstreak_width", "20", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iKillstreakImageWidth, "killstreak_image_width", "20", "proportional_int" );
+
+ CTFPlayerModelPanel *m_pPlayerModelPanel;
+ int m_nPlayerModelPanelIndex;
+
+ bool m_bRedScrollBarVisible;
+ bool m_bBlueScrollBarVisible;
+ int m_nExtraSpace;
+
+ CExLabel *m_pRedTeamName;
+ CExLabel *m_pBlueTeamName;
+
+ CAvatarImagePanel *m_pRedLeaderAvatarImage;
+ EditablePanel *m_pRedLeaderAvatarBG;
+ vgui::ImagePanel *m_pRedTeamImage;
+ CAvatarImagePanel *m_pBlueLeaderAvatarImage;
+ EditablePanel *m_pBlueLeaderAvatarBG;
+ vgui::ImagePanel *m_pBlueTeamImage;
+
+ CUtlVector< CTFBadgePanel* > m_pBlueBadgePanels;
+ CUtlVector< CTFBadgePanel* > m_pRedBadgePanels;
+
+ CHandle< C_TFPlayer > m_hSelectedPlayer;
+ bool m_bUsePlayerModel;
+};
+
+const wchar_t *GetPointsString( int iPoints );
+
+#endif // TF_SCOREBOARD_H
diff --git a/game/client/tf/vgui/tf_controls.cpp b/game/client/tf/vgui/tf_controls.cpp
new file mode 100644
index 0000000..23f61b2
--- /dev/null
+++ b/game/client/tf/vgui/tf_controls.cpp
@@ -0,0 +1,1292 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+#include "ienginevgui.h"
+#include <vgui_controls/ScrollBarSlider.h>
+#include "vgui/ILocalize.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "tf_controls.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/PropertyPage.h"
+#include "econ_item_system.h"
+#include "iachievementmgr.h"
+#include <vgui_controls/ListPanel.h>
+#include <vgui_controls/PanelListPanel.h>
+#include <vgui_controls/Label.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/MessageBox.h>
+#include <vgui_controls/CheckButton.h>
+#include <vgui_controls/ComboBox.h>
+#include <vgui_controls/TextEntry.h>
+#include <../common/GameUI/cvarslider.h>
+#include "filesystem.h"
+
+using namespace vgui;
+
+wchar_t* LocalizeNumberWithToken( const char* pszLocToken, int nValue )
+{
+ static wchar_t wszOutString[ 128 ];
+ wchar_t wszCount[ 16 ];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", nValue );
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( pszLocToken );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, wszCount );
+
+ return wszOutString;
+}
+
+DECLARE_BUILD_FACTORY( CExCheckButton );
+DECLARE_BUILD_FACTORY( CTFFooter );
+
+//-----------------------------------------------------------------------------
+// Purpose: Xbox-specific panel that displays button icons text labels
+//-----------------------------------------------------------------------------
+CTFFooter::CTFFooter( Panel *parent, const char *panelName ) : BaseClass( parent, panelName )
+{
+ SetVisible( true );
+ SetAlpha( 0 );
+
+ m_nButtonGap = 32;
+ m_ButtonPinRight = 100;
+ m_FooterTall = 80;
+
+ m_ButtonOffsetFromTop = 0;
+ m_ButtonSeparator = 4;
+ m_TextAdjust = 0;
+
+ m_bPaintBackground = false;
+ m_bCenterHorizontal = true;
+
+ m_szButtonFont[0] = '\0';
+ m_szTextFont[0] = '\0';
+ m_szFGColor[0] = '\0';
+ m_szBGColor[0] = '\0';
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFFooter::~CTFFooter()
+{
+ ClearButtons();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFooter::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_hButtonFont = pScheme->GetFont( ( m_szButtonFont[0] != '\0' ) ? m_szButtonFont : "GameUIButtons" );
+ m_hTextFont = pScheme->GetFont( ( m_szTextFont[0] != '\0' ) ? m_szTextFont : "MenuLarge" );
+
+ SetFgColor( pScheme->GetColor( m_szFGColor, Color( 255, 255, 255, 255 ) ) );
+ SetBgColor( pScheme->GetColor( m_szBGColor, Color( 0, 0, 0, 255 ) ) );
+
+ int x, y, w, h;
+ GetParent()->GetBounds( x, y, w, h );
+ SetBounds( x, h - m_FooterTall, w, m_FooterTall );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFooter::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ // gap between hints
+ m_nButtonGap = inResourceData->GetInt( "buttongap", 32 );
+ m_ButtonPinRight = inResourceData->GetInt( "button_pin_right", 100 );
+ m_FooterTall = inResourceData->GetInt( "tall", 80 );
+ m_ButtonOffsetFromTop = inResourceData->GetInt( "buttonoffsety", 0 );
+ m_ButtonSeparator = inResourceData->GetInt( "button_separator", 4 );
+ m_TextAdjust = inResourceData->GetInt( "textadjust", 0 );
+
+ m_bCenterHorizontal = ( inResourceData->GetInt( "center", 1 ) == 1 );
+ m_bPaintBackground = ( inResourceData->GetInt( "paintbackground", 0 ) == 1 );
+
+ // fonts for text and button
+ Q_strncpy( m_szTextFont, inResourceData->GetString( "fonttext", "MenuLarge" ), sizeof( m_szTextFont ) );
+ Q_strncpy( m_szButtonFont, inResourceData->GetString( "fontbutton", "GameUIButtons" ), sizeof( m_szButtonFont ) );
+
+ // fg and bg colors
+ Q_strncpy( m_szFGColor, inResourceData->GetString( "fgcolor", "White" ), sizeof( m_szFGColor ) );
+ Q_strncpy( m_szBGColor, inResourceData->GetString( "bgcolor", "Black" ), sizeof( m_szBGColor ) );
+
+ // clear the buttons because we're going to re-add them here
+ ClearButtons();
+
+ for ( KeyValues *pButton = inResourceData->GetFirstSubKey(); pButton != NULL; pButton = pButton->GetNextKey() )
+ {
+ const char *pNameButton = pButton->GetName();
+
+ if ( !Q_stricmp( pNameButton, "button" ) )
+ {
+ // Add a button to the footer
+ const char *pName = pButton->GetString( "name", "NULL" );
+ const char *pText = pButton->GetString( "text", "NULL" );
+ const char *pIcon = pButton->GetString( "icon", "NULL" );
+ AddNewButtonLabel( pName, pText, pIcon );
+ }
+ }
+
+ InvalidateLayout( false, true ); // force ApplySchemeSettings to run
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFooter::AddNewButtonLabel( const char *name, const char *text, const char *icon )
+{
+ FooterButton_t *button = new FooterButton_t;
+
+ button->bVisible = true;
+ Q_strncpy( button->name, name, sizeof( button->name ) );
+
+ // Button icons are a single character
+ wchar_t *pIcon = g_pVGuiLocalize->Find( icon );
+ if ( pIcon )
+ {
+ button->icon[0] = pIcon[0];
+ button->icon[1] = '\0';
+ }
+ else
+ {
+ button->icon[0] = '\0';
+ }
+
+ // Set the help text
+ wchar_t *pText = g_pVGuiLocalize->Find( text );
+ if ( pText )
+ {
+ wcsncpy( button->text, pText, wcslen( pText ) + 1 );
+ }
+ else
+ {
+ button->text[0] = '\0';
+ }
+
+ m_Buttons.AddToTail( button );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFooter::ShowButtonLabel( const char *name, bool show )
+{
+ for ( int i = 0; i < m_Buttons.Count(); ++i )
+ {
+ if ( !Q_stricmp( m_Buttons[ i ]->name, name ) )
+ {
+ m_Buttons[ i ]->bVisible = show;
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFooter::PaintBackground( void )
+{
+ if ( !m_bPaintBackground )
+ return;
+
+ BaseClass::PaintBackground();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFooter::Paint( void )
+{
+ // inset from right edge
+ int wide = GetWide();
+
+ // center the text within the button
+ int buttonHeight = vgui::surface()->GetFontTall( m_hButtonFont );
+ int fontHeight = vgui::surface()->GetFontTall( m_hTextFont );
+ int textY = ( buttonHeight - fontHeight )/2 + m_TextAdjust;
+
+ if ( textY < 0 )
+ {
+ textY = 0;
+ }
+
+ int y = m_ButtonOffsetFromTop;
+
+ if ( !m_bCenterHorizontal )
+ {
+ // draw the buttons, right to left
+ int x = wide - m_ButtonPinRight;
+
+ vgui::Label label( this, "temp", L"" );
+ for ( int i = m_Buttons.Count() - 1 ; i >= 0 ; --i )
+ {
+ FooterButton_t *pButton = m_Buttons[i];
+ if ( !pButton->bVisible )
+ continue;
+
+ // Get the string length
+ label.SetFont( m_hTextFont );
+ label.SetText( pButton->text );
+ label.SizeToContents();
+
+ int iTextWidth = label.GetWide();
+
+ if ( iTextWidth == 0 )
+ x += m_nButtonGap; // There's no text, so remove the gap between buttons
+ else
+ x -= iTextWidth;
+
+ // Draw the string
+ vgui::surface()->DrawSetTextFont( m_hTextFont );
+ vgui::surface()->DrawSetTextColor( GetFgColor() );
+ vgui::surface()->DrawSetTextPos( x, y + textY );
+ vgui::surface()->DrawPrintText( pButton->text, wcslen( pButton->text ) );
+
+ // Draw the button
+ // back up button width and a little extra to leave a gap between button and text
+ x -= ( vgui::surface()->GetCharacterWidth( m_hButtonFont, pButton->icon[0] ) + m_ButtonSeparator );
+ vgui::surface()->DrawSetTextFont( m_hButtonFont );
+ vgui::surface()->DrawSetTextColor( 255, 255, 255, 255 );
+ vgui::surface()->DrawSetTextPos( x, y );
+ vgui::surface()->DrawPrintText( pButton->icon, 1 );
+
+ // back up to next string
+ x -= m_nButtonGap;
+ }
+ }
+ else
+ {
+ // center the buttons (as a group)
+ int x = wide / 2;
+ int totalWidth = 0;
+ int i = 0;
+ int nButtonCount = 0;
+
+ vgui::Label label( this, "temp", L"" );
+
+ // need to loop through and figure out how wide our buttons and text are (with gaps between) so we can offset from the center
+ for ( i = 0; i < m_Buttons.Count(); ++i )
+ {
+ FooterButton_t *pButton = m_Buttons[i];
+
+ if ( !pButton->bVisible )
+ continue;
+
+ // Get the string length
+ label.SetFont( m_hTextFont );
+ label.SetText( pButton->text );
+ label.SizeToContents();
+
+ totalWidth += vgui::surface()->GetCharacterWidth( m_hButtonFont, pButton->icon[0] );
+ totalWidth += m_ButtonSeparator;
+ totalWidth += label.GetWide();
+
+ nButtonCount++; // keep track of how many active buttons we'll be drawing
+ }
+
+ totalWidth += ( nButtonCount - 1 ) * m_nButtonGap; // add in the gaps between the buttons
+ x -= ( totalWidth / 2 );
+
+ for ( i = 0; i < m_Buttons.Count(); ++i )
+ {
+ FooterButton_t *pButton = m_Buttons[i];
+
+ if ( !pButton->bVisible )
+ continue;
+
+ // Get the string length
+ label.SetFont( m_hTextFont );
+ label.SetText( pButton->text );
+ label.SizeToContents();
+
+ int iTextWidth = label.GetWide();
+
+ // Draw the icon
+ vgui::surface()->DrawSetTextFont( m_hButtonFont );
+ vgui::surface()->DrawSetTextColor( 255, 255, 255, 255 );
+ vgui::surface()->DrawSetTextPos( x, y );
+ vgui::surface()->DrawPrintText( pButton->icon, 1 );
+ x += vgui::surface()->GetCharacterWidth( m_hButtonFont, pButton->icon[0] ) + m_ButtonSeparator;
+
+ // Draw the string
+ vgui::surface()->DrawSetTextFont( m_hTextFont );
+ vgui::surface()->DrawSetTextColor( GetFgColor() );
+ vgui::surface()->DrawSetTextPos( x, y + textY );
+ vgui::surface()->DrawPrintText( pButton->text, wcslen( pButton->text ) );
+
+ x += iTextWidth + m_nButtonGap;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFooter::ClearButtons( void )
+{
+ m_Buttons.PurgeAndDeleteElements();
+}
+
+#define OPTIONS_DIR "cfg"
+#define DEFAULT_OPTIONS_FILE OPTIONS_DIR "/user_default.scr"
+#define OPTIONS_FILE OPTIONS_DIR "/user.scr"
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFAdvancedOptionsDialog::CTFAdvancedOptionsDialog(vgui::Panel *parent) : BaseClass(NULL, "TFAdvancedOptionsDialog")
+{
+ // Need to use the clientscheme (we're not parented to a clientscheme'd panel)
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ m_pListPanel = new vgui::PanelListPanel( this, "PanelListPanel" );
+
+ m_pList = NULL;
+
+ m_pToolTip = new CTFTextToolTip( this );
+ m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" );
+ m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
+ m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
+ m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel );
+ m_pToolTip->SetTooltipDelay( 0 );
+
+ m_pDescription = new CInfoDescription();
+ m_pDescription->InitFromFile( DEFAULT_OPTIONS_FILE );
+ m_pDescription->InitFromFile( OPTIONS_FILE, false );
+ m_pDescription->TransferCurrentValues( NULL );
+
+// MoveToCenterOfScreen();
+// SetSizeable( false );
+// SetDeleteSelfOnClose( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CTFAdvancedOptionsDialog::~CTFAdvancedOptionsDialog()
+{
+ delete m_pDescription;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings("resource/ui/TFAdvancedOptionsDialog.res");
+ m_pListPanel->SetFirstColumnWidth( 0 );
+
+ CreateControls();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::OnClose()
+{
+ BaseClass::OnClose();
+
+ TFModalStack()->PopModal( this );
+ MarkForDeletion();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *command -
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::OnCommand( const char *command )
+{
+ if ( !stricmp( command, "Ok" ) )
+ {
+ // OnApplyChanges();
+ SaveValues();
+ OnClose();
+ return;
+ }
+ else if ( !stricmp( command, "Close" ) )
+ {
+ OnClose();
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CTFAdvancedOptionsDialog::OnKeyCodeTyped(KeyCode code)
+{
+ // force ourselves to be closed if the escape key it pressed
+ if ( code == KEY_ESCAPE )
+ {
+ OnClose();
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped(code);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::OnKeyCodePressed(KeyCode code)
+{
+ // force ourselves to be closed if the escape key it pressed
+ if ( GetBaseButtonCode( code ) == KEY_XBUTTON_B || GetBaseButtonCode( code ) == STEAMCONTROLLER_B || GetBaseButtonCode( code ) == STEAMCONTROLLER_START )
+ {
+ OnClose();
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed(code);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::GatherCurrentValues()
+{
+ if ( !m_pDescription )
+ return;
+
+ // OK
+ CheckButton *pBox;
+ TextEntry *pEdit;
+ ComboBox *pCombo;
+ CCvarSlider *pSlider;
+
+ mpcontrol_t *pList;
+
+ CScriptObject *pObj;
+ CScriptListItem *pItem;
+
+ char szValue[256];
+ char strValue[ 256 ];
+
+ pList = m_pList;
+ while ( pList )
+ {
+ pObj = pList->pScrObj;
+
+ if ( pObj->type == O_CATEGORY )
+ {
+ pList = pList->next;
+ continue;
+ }
+
+ if ( !pList->pControl )
+ {
+ pObj->SetCurValue( pObj->defValue );
+ pList = pList->next;
+ continue;
+ }
+
+ switch ( pObj->type )
+ {
+ case O_BOOL:
+ pBox = (CheckButton *)pList->pControl;
+ sprintf( szValue, "%s", pBox->IsSelected() ? "1" : "0" );
+ break;
+ case O_NUMBER:
+ pEdit = ( TextEntry * )pList->pControl;
+ pEdit->GetText( strValue, sizeof( strValue ) );
+ sprintf( szValue, "%s", strValue );
+ break;
+ case O_STRING:
+ pEdit = ( TextEntry * )pList->pControl;
+ pEdit->GetText( strValue, sizeof( strValue ) );
+ sprintf( szValue, "%s", strValue );
+ break;
+ case O_LIST:
+ {
+ pCombo = (ComboBox *)pList->pControl;
+ // pCombo->GetText( strValue, sizeof( strValue ) );
+ int activeItem = pCombo->GetActiveItem();
+
+ pItem = pObj->pListItems;
+ // int n = (int)pObj->fdefValue;
+
+ while ( pItem )
+ {
+ if (!activeItem--)
+ break;
+
+ pItem = pItem->pNext;
+ }
+
+ if ( pItem )
+ {
+ sprintf( szValue, "%s", pItem->szValue );
+ }
+ else // Couln't find index
+ {
+ //assert(!("Couldn't find string in list, using default value"));
+ sprintf( szValue, "%s", pObj->defValue );
+ }
+ break;
+ }
+ case O_SLIDER:
+ pSlider = ( CCvarSlider * )pList->pControl;
+ sprintf( szValue, "%.2f", pSlider->GetSliderValue() );
+ break;
+ }
+
+ // Remove double quotes and % characters
+ UTIL_StripInvalidCharacters( szValue, sizeof(szValue) );
+
+ V_strcpy_safe( strValue, szValue );
+
+ pObj->SetCurValue( strValue );
+
+ pList = pList->next;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::CreateControls()
+{
+ DestroyControls();
+
+ // Go through desciption creating controls
+ CScriptObject *pObj;
+
+ pObj = m_pDescription->pObjList;
+
+ // Build out the clan dropdown
+ CScriptObject *pClanObj = m_pDescription->FindObject( "cl_clanid" );
+ ISteamFriends *pFriends = steamapicontext->SteamFriends();
+ if ( pFriends && pClanObj )
+ {
+ pClanObj->RemoveAndDeleteAllItems();
+ int iGroupCount = pFriends->GetClanCount();
+ pClanObj->AddItem( new CScriptListItem( "#Cstrike_ClanTag_None", "0" ) );
+ for ( int k = 0; k < iGroupCount; ++ k )
+ {
+ CSteamID clanID = pFriends->GetClanByIndex( k );
+ const char *pName = pFriends->GetClanName( clanID );
+ const char *pTag = pFriends->GetClanTag( clanID );
+
+ char id[12];
+ Q_snprintf( id, sizeof( id ), "%d", clanID.GetAccountID() );
+ pClanObj->AddItem( new CScriptListItem( CFmtStr( "%s (%s)", pTag, pName ), id ) );
+ }
+ }
+
+ mpcontrol_t *pCtrl;
+
+ CheckButton *pBox;
+ TextEntry *pEdit;
+ ComboBox *pCombo;
+ CCvarSlider *pSlider;
+ CScriptListItem *pListItem;
+
+ Panel *objParent = m_pListPanel;
+
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ vgui::HFont hTextFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ Color tanDark = pScheme->GetColor( "TanDark", Color(255,0,0,255) );
+
+ while ( pObj )
+ {
+ if ( pObj->type == O_OBSOLETE )
+ {
+ pObj = pObj->pNext;
+ continue;
+ }
+
+ pCtrl = new mpcontrol_t( objParent, "mpcontrol_t" );
+ pCtrl->type = pObj->type;
+
+ // Force it to invalidate scheme now, so we can change color afterwards and have it persist
+ pCtrl->InvalidateLayout( true, true );
+
+ switch ( pCtrl->type )
+ {
+ case O_BOOL:
+ pBox = new CheckButton( pCtrl, "DescCheckButton", pObj->prompt );
+ pBox->SetSelected( pObj->fdefValue != 0.0f ? true : false );
+
+ pCtrl->pControl = (Panel *)pBox;
+ pBox->SetFont( hTextFont );
+
+ pBox->InvalidateLayout( true, true );
+
+ // This is utterly fucking retarded.
+ pBox->SetFgColor( tanDark );
+ pBox->SetDefaultColor( tanDark, pBox->GetBgColor() );
+ pBox->SetArmedColor( tanDark, pBox->GetBgColor() );
+ pBox->SetDepressedColor( tanDark, pBox->GetBgColor() );
+ pBox->SetSelectedColor( tanDark, pBox->GetBgColor() );
+ pBox->SetHighlightColor( tanDark );
+ pBox->GetCheckImage()->SetColor( tanDark );
+ break;
+ case O_STRING:
+ case O_NUMBER:
+ pEdit = new TextEntry( pCtrl, "DescTextEntry");
+ pEdit->InsertString(pObj->defValue);
+ pCtrl->pControl = (Panel *)pEdit;
+ pEdit->SetFont( hTextFont );
+
+ pEdit->InvalidateLayout( true, true );
+ pEdit->SetBgColor( Color(0,0,0,255) );
+ break;
+ case O_LIST:
+ {
+ pCombo = new ComboBox( pCtrl, "DescComboBox", 5, false );
+
+ // track which row matches the current value
+ int iRow = -1;
+ int iCount = 0;
+ pListItem = pObj->pListItems;
+ while ( pListItem )
+ {
+ if ( iRow == -1 && !Q_stricmp( pListItem->szValue, pObj->curValue ) )
+ iRow = iCount;
+
+ pCombo->AddItem( pListItem->szItemText, NULL );
+ pListItem = pListItem->pNext;
+ ++iCount;
+ }
+
+
+ pCombo->ActivateItemByRow( iRow );
+
+ pCtrl->pControl = (Panel *)pCombo;
+ pCombo->SetFont( hTextFont );
+ }
+ break;
+ case O_SLIDER:
+ pSlider = new CCvarSlider( pCtrl, "DescSlider", "Test", pObj->fMin, pObj->fMax, pObj->cvarname, false );
+ pCtrl->pControl = (Panel *)pSlider;
+ break;
+ case O_CATEGORY:
+ pCtrl->SetBorder( pScheme->GetBorder("OptionsCategoryBorder") );
+ break;
+ default:
+ break;
+ }
+
+ if ( pCtrl->type != O_BOOL )
+ {
+ pCtrl->pPrompt = new vgui::Label( pCtrl, "DescLabel", "" );
+ pCtrl->pPrompt->SetContentAlignment( vgui::Label::a_west );
+ pCtrl->pPrompt->SetTextInset( 5, 0 );
+ pCtrl->pPrompt->SetText( pObj->prompt );
+ pCtrl->pPrompt->SetFont( hTextFont );
+
+ pCtrl->pPrompt->InvalidateLayout( true, true );
+
+ if ( pCtrl->type == O_CATEGORY )
+ {
+ pCtrl->pPrompt->SetFont( pScheme->GetFont( "HudFontSmallBold", true ) );
+ pCtrl->pPrompt->SetFgColor( pScheme->GetColor( "TanLight", Color(255,0,0,255) ) );
+ }
+ else
+ {
+ pCtrl->pPrompt->SetFgColor( tanDark );
+ }
+ }
+
+ pCtrl->pScrObj = pObj;
+
+ switch ( pCtrl->type )
+ {
+ case O_BOOL:
+ case O_STRING:
+ case O_NUMBER:
+ case O_LIST:
+ case O_CATEGORY:
+ pCtrl->SetSize( m_iControlW, m_iControlH );
+ break;
+ case O_SLIDER:
+ pCtrl->SetSize( m_iSliderW, m_iSliderH );
+ break;
+ default:
+ break;
+ }
+
+ // Hook up the tooltip, if the entry has one
+ if ( pObj->tooltip && pObj->tooltip[0] )
+ {
+ if ( pCtrl->pPrompt )
+ {
+ pCtrl->pPrompt->SetTooltip( m_pToolTip, pObj->tooltip );
+ }
+ else
+ {
+ pCtrl->SetTooltip( m_pToolTip, pObj->tooltip );
+ pCtrl->pControl->SetTooltip( m_pToolTip, pObj->tooltip );
+ }
+ }
+
+ m_pListPanel->AddItem( NULL, pCtrl );
+
+ // Link it in
+ if ( !m_pList )
+ {
+ m_pList = pCtrl;
+ pCtrl->next = NULL;
+ }
+ else
+ {
+ mpcontrol_t *p;
+ p = m_pList;
+ while ( p )
+ {
+ if ( !p->next )
+ {
+ p->next = pCtrl;
+ pCtrl->next = NULL;
+ break;
+ }
+ p = p->next;
+ }
+ }
+
+ pObj = pObj->pNext;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::DestroyControls()
+{
+ mpcontrol_t *p, *n;
+
+ p = m_pList;
+ while ( p )
+ {
+ n = p->next;
+ //
+ if ( p->pControl )
+ {
+ p->pControl->MarkForDeletion();
+ p->pControl = NULL;
+ }
+ if ( p->pPrompt )
+ {
+ p->pPrompt->MarkForDeletion();
+ p->pPrompt = NULL;
+ }
+ delete p;
+ p = n;
+ }
+
+ m_pList = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::SaveValues()
+{
+ // Get the values from the controls:
+ GatherCurrentValues();
+
+ // Create the game.cfg file
+ if ( m_pDescription )
+ {
+ FileHandle_t fp;
+
+ // Add settings to config.cfg
+ m_pDescription->WriteToConfig();
+
+ g_pFullFileSystem->CreateDirHierarchy( OPTIONS_DIR );
+ fp = g_pFullFileSystem->Open( OPTIONS_FILE, "wb" );
+ if ( fp )
+ {
+ m_pDescription->WriteToScriptFile( fp );
+ g_pFullFileSystem->Close( fp );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFAdvancedOptionsDialog::Deploy( void )
+{
+ SetVisible( true );
+ MakePopup();
+ MoveToFront();
+ SetKeyBoardInputEnabled(true);
+ SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( this );
+
+ // Center it, keeping requested size
+ int x, y, ww, wt, wide, tall;
+ vgui::surface()->GetWorkspaceBounds( x, y, ww, wt );
+ GetSize(wide, tall);
+ SetPos(x + ((ww - wide) / 2), y + ((wt - tall) / 2));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextToolTip::PerformLayout()
+{
+ if ( !ShouldLayout() )
+ return;
+
+ _isDirty = false;
+
+ // Resize our text labels to fit.
+ int iW = m_pEmbeddedPanel->GetWide();
+ int iH = 0;
+ for (int i = 0; i < m_pEmbeddedPanel->GetChildCount(); i++)
+ {
+ vgui::Label *pLabel = dynamic_cast<vgui::Label*>( m_pEmbeddedPanel->GetChild(i) );
+ if ( !pLabel )
+ continue;
+
+ // Only checking to see if we have any text
+ char szTmp[2];
+ pLabel->GetText( szTmp, sizeof(szTmp) );
+ if ( !szTmp[0] )
+ continue;
+
+ int iLX, iLY;
+ pLabel->GetPos( iLX, iLY );
+
+ int iMaxWidth = m_pEmbeddedPanel->GetWide() - (iLX * 2);
+ pLabel->GetTextImage()->ResizeImageToContentMaxWidth( iMaxWidth );
+ pLabel->SizeToContents();
+ pLabel->SetWide( iMaxWidth );
+ pLabel->InvalidateLayout(true);
+
+ int iX, iY;
+ pLabel->GetPos( iX, iY );
+ iW = MAX( iW, ( pLabel->GetWide() + (iX * 2) ) );
+
+ if ( iH == 0 )
+ {
+ iH += MAX( iH, pLabel->GetTall() + (iY * 2) );
+ }
+ else
+ {
+ iH += MAX( iH, pLabel->GetTall() );
+ }
+ }
+ m_pEmbeddedPanel->SetSize( m_pEmbeddedPanel->GetWide(), iH );
+
+ m_pEmbeddedPanel->SetVisible(true);
+
+ PositionWindow( m_pEmbeddedPanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextToolTip::PositionWindow( Panel *pTipPanel )
+{
+ int iTipW, iTipH;
+ pTipPanel->GetSize( iTipW, iTipH );
+
+ int cursorX, cursorY;
+ input()->GetCursorPos(cursorX, cursorY);
+
+ int px, py, wide, tall;
+ ipanel()->GetAbsPos( m_pEmbeddedPanel->GetParent()->GetVPanel(), px, py );
+ m_pEmbeddedPanel->GetParent()->GetSize(wide, tall);
+
+ if ( !m_pEmbeddedPanel->IsPopup() )
+ {
+ // Move the cursor into our parent space
+ cursorX -= px;
+ cursorY -= py;
+ }
+
+ if (wide - iTipW > cursorX)
+ {
+ cursorY += 20;
+ // menu hanging right
+ if (tall - iTipH > cursorY)
+ {
+ // menu hanging down
+ pTipPanel->SetPos(cursorX, cursorY);
+ }
+ else
+ {
+ // menu hanging up
+ pTipPanel->SetPos(cursorX, cursorY - iTipH - 20);
+ }
+ }
+ else
+ {
+ // menu hanging left
+ if (tall - iTipH > cursorY)
+ {
+ // menu hanging down
+ pTipPanel->SetPos( Max( 0, cursorX - iTipW ), cursorY);
+ }
+ else
+ {
+ // menu hanging up
+ pTipPanel->SetPos( Max( 0, cursorX - iTipW ), cursorY - iTipH - 20 );
+ }
+ }
+}
+
+static vgui::DHANDLE<CTFAdvancedOptionsDialog> g_pTFAdvancedOptionsDialog;
+
+//-----------------------------------------------------------------------------
+// Purpose: Callback to open the game menus
+//-----------------------------------------------------------------------------
+void CL_OpenTFAdvancedOptionsDialog( const CCommand &args )
+{
+ if ( g_pTFAdvancedOptionsDialog.Get() == NULL )
+ {
+ g_pTFAdvancedOptionsDialog = vgui::SETUP_PANEL( new CTFAdvancedOptionsDialog( NULL ) );
+ }
+
+ g_pTFAdvancedOptionsDialog->Deploy();
+}
+
+// the console commands
+static ConCommand opentf2options( "opentf2options", &CL_OpenTFAdvancedOptionsDialog, "Displays the TF2 Advanced Options dialog." );
+
+//-----------------------------------------------------------------------------
+// Purpose: A scroll bar that can have specified width
+//-----------------------------------------------------------------------------
+class CExScrollBar : public ScrollBar
+{
+ DECLARE_CLASS_SIMPLE( CExScrollBar, ScrollBar );
+public:
+
+ CExScrollBar( Panel *parent, const char *name, bool bVertical )
+ : ScrollBar( parent, name, bVertical )
+ {}
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE
+ {
+ // Deliberately skip ScrollBar
+ Panel::ApplySchemeSettings( pScheme );
+ }
+};
+
+DECLARE_BUILD_FACTORY( CExScrollingEditablePanel );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExScrollingEditablePanel::CExScrollingEditablePanel( Panel *pParent, const char *pszName )
+ : EditablePanel( pParent, pszName )
+ , m_nLastScrollValue( 0 )
+ , m_bUseMouseWheelToScroll( true )
+{
+ m_pScrollBar = new CExScrollBar( this, "ScrollBar", true );
+ m_pScrollBar->AddActionSignalTarget( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExScrollingEditablePanel::~CExScrollingEditablePanel()
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExScrollingEditablePanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pScrollbarKV = inResourceData->FindKey( "Scrollbar" );
+ if ( pScrollbarKV )
+ {
+ m_pScrollBar->ApplySettings( pScrollbarKV );
+ }
+
+ m_bUseMouseWheelToScroll = inResourceData->GetBool( "allow_mouse_wheel_to_scroll", true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExScrollingEditablePanel::OnSizeChanged( int newWide, int newTall )
+{
+ BaseClass::OnSizeChanged( newWide, newTall );
+
+ int nDelta = m_nLastScrollValue;
+ // Go through all our children and move them BACK into position
+ int nNumChildren = GetChildCount();
+ for ( int i=0; i < nNumChildren; ++i )
+ {
+ Panel* pChild = GetChild( i );
+
+ if ( pChild == m_pScrollBar )
+ continue;
+
+ EditablePanel* pEditableChild = dynamic_cast< EditablePanel* >( pChild );
+ if ( pEditableChild && pEditableChild->ShouldSkipAutoResize() )
+ continue;
+
+ int x,y;
+ pChild->GetPos( x, y );
+
+ pChild->SetPos( x, y - nDelta );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExScrollingEditablePanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ int nFurthestY = 0;
+
+ // Go through all our children and find the lowest point on the lowest child
+ // that we'd need to scroll to
+ int nNumChildren = GetChildCount();
+ for ( int i=0; i < nNumChildren; ++i )
+ {
+ Panel* pChild = GetChild( i );
+
+ if ( pChild == m_pScrollBar )
+ continue;
+
+ int x,y,wide,tall;
+ pChild->GetBounds( x, y, wide, tall );
+
+ // Offset by our scroll value
+ y += m_nLastScrollValue;
+
+ if ( m_bRestrictWidth )
+ {
+ int nMaxWide = Min( x + wide, GetWide() - m_pScrollBar->GetWide() );
+ pChild->SetWide( nMaxWide - x );
+ }
+
+ nFurthestY = Max( y + tall, nFurthestY );
+ }
+
+ int nMaxRange = nFurthestY + m_iBottomBuffer;
+ m_pScrollBar->SetRange( 0, nMaxRange );
+ m_pScrollBar->SetRangeWindow( GetTall() );
+
+ OnScrollBarSliderMoved();
+}
+
+//-----------------------------------------------------------------------------
+// Called when the scroll bar moves
+//-----------------------------------------------------------------------------
+void CExScrollingEditablePanel::OnScrollBarSliderMoved()
+{
+ // Figure out how far they just scrolled
+ int nScrollAmount = m_pScrollBar->GetValue();
+ int nDelta = nScrollAmount - m_nLastScrollValue;
+
+ if ( nDelta == 0 )
+ return;
+
+ ShiftChildren( nDelta );
+
+ m_nLastScrollValue = nScrollAmount;
+}
+
+void CExScrollingEditablePanel::ShiftChildren( int nDistance )
+{
+ // Go through all our children and move them
+ int nNumChildren = GetChildCount();
+ for ( int i=0; i < nNumChildren; ++i )
+ {
+ Panel* pChild = GetChild( i );
+
+ if ( pChild == m_pScrollBar )
+ continue;
+
+ int x,y;
+ pChild->GetPos( x, y );
+
+ pChild->SetPos( x, y - nDistance );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// respond to mouse wheel events
+//-----------------------------------------------------------------------------
+void CExScrollingEditablePanel::OnMouseWheeled( int delta )
+{
+ if ( !m_bUseMouseWheelToScroll )
+ {
+ BaseClass::OnMouseWheeled( delta );
+ return;
+ }
+
+ int val = m_pScrollBar->GetValue();
+ val -= ( delta * m_iScrollStep );
+ m_pScrollBar->SetValue( val );
+}
+
+DECLARE_BUILD_FACTORY( CScrollableList );
+//-----------------------------------------------------------------------------
+// Clearnup
+//-----------------------------------------------------------------------------
+CScrollableList::~CScrollableList()
+{
+ ClearAutoLayoutPanels();
+}
+
+void CScrollableList::PerformLayout()
+{
+ int nYpos = -m_nLastScrollValue;
+
+ for( int i=0; i<m_vecAutoLayoutPanels.Count(); ++i )
+ {
+ LayoutInfo_t layout = m_vecAutoLayoutPanels[ i ];
+ nYpos += layout.m_nGap;
+
+ layout.m_pPanel->SetPos( layout.m_pPanel->GetXPos(), nYpos );
+
+ nYpos += layout.m_pPanel->GetTall();
+ }
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Add a panel to the bottom
+//-----------------------------------------------------------------------------
+void CScrollableList::AddPanel( Panel* pPanel, int nGap )
+{
+ // We're the captain now
+ pPanel->SetParent( this );
+ pPanel->SetAutoDelete( false );
+
+ auto idx = m_vecAutoLayoutPanels.AddToTail();
+ LayoutInfo_t& layout = m_vecAutoLayoutPanels[ idx ];
+ layout.m_pPanel = pPanel;
+ layout.m_nGap = nGap;
+
+ // Need to do a perform layout so we get sized correctly
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Delete any panels we own
+//-----------------------------------------------------------------------------
+void CScrollableList::ClearAutoLayoutPanels()
+{
+ FOR_EACH_VEC( m_vecAutoLayoutPanels, i )
+ {
+ m_vecAutoLayoutPanels[ i ].m_pPanel->MarkForDeletion();
+ }
+
+ m_vecAutoLayoutPanels.Purge();
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CExpandablePanel::CExpandablePanel( Panel* pParent, const char* pszName )
+ : vgui::EditablePanel( pParent, pszName )
+ , m_bExpanded( false )
+ , m_flAnimEndTime( 0.f )
+ , m_flResizeTime( 0.f )
+{}
+
+
+//-----------------------------------------------------------------------------
+// Set spcific collapsed state
+//-----------------------------------------------------------------------------
+void CExpandablePanel::SetCollapsed( bool bCollapsed )
+{
+ if ( bCollapsed == m_bExpanded )
+ {
+ ToggleCollapse();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Toggle collapsed state
+//-----------------------------------------------------------------------------
+void CExpandablePanel::ToggleCollapse()
+{
+ m_bExpanded = !m_bExpanded;
+ // Allow for quick bounce-back if they click while we're already animating
+ float flEndTime = RemapValClamped( GetPercentAnimated(), 0.f, 1.f, 0.f, m_flResizeTime );
+ m_flAnimEndTime = Plat_FloatTime() + flEndTime;
+
+ OnToggleCollapse( m_bExpanded );
+}
+
+//-----------------------------------------------------------------------------
+// Toggle collapsed state
+//-----------------------------------------------------------------------------
+void CExpandablePanel::OnCommand( const char *command )
+{
+ if ( FStrEq( "toggle_collapse", command ) )
+ {
+ ToggleCollapse();
+
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Do collapsing interpolation
+//-----------------------------------------------------------------------------
+void CExpandablePanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ float flTimeProgress = Gain( GetPercentAnimated(), 0.8f );
+
+ const int& nStartHeight = m_bExpanded ? m_nCollapsedHeight : m_nExpandedHeight;
+ const int& nEndHeight = m_bExpanded ? m_nExpandedHeight : m_nCollapsedHeight;
+ int nCurrentHeight = RemapValClamped( flTimeProgress, 0.f, 1.f, nStartHeight, nEndHeight );
+
+ if ( nCurrentHeight != GetTall() )
+ {
+ SetTall( nCurrentHeight );
+ Panel* pParent = GetParent();
+ if ( pParent )
+ {
+ pParent->InvalidateLayout();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Where we're at in our interpolation
+//-----------------------------------------------------------------------------
+float CExpandablePanel::GetPercentAnimated() const
+{
+ return RemapValClamped( Plat_FloatTime() - ( m_flAnimEndTime - m_flResizeTime ), 0.f, m_flResizeTime, 0.f, 1.f );
+}
+
diff --git a/game/client/tf/vgui/tf_controls.h b/game/client/tf/vgui/tf_controls.h
new file mode 100644
index 0000000..d888ef6
--- /dev/null
+++ b/game/client/tf/vgui/tf_controls.h
@@ -0,0 +1,308 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_CONTROLS_H
+#define TF_CONTROLS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui/IScheme.h>
+#include <vgui/KeyCode.h>
+#include <KeyValues.h>
+#include <vgui/IVGui.h>
+#include <vgui_controls/ScrollBar.h>
+#include <vgui_controls/EditablePanel.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/Label.h>
+#include <vgui_controls/RichText.h>
+#include "utlvector.h"
+#include "vgui_controls/PHandle.h"
+#include <vgui_controls/Tooltip.h>
+#include "econ_controls.h"
+#include "sc_hinticon.h"
+#if defined( TF_CLIENT_DLL )
+#include "tf_shareddefs.h"
+#include "tf_imagepanel.h"
+#endif
+#include <vgui_controls/Frame.h>
+#include <../common/GameUI/scriptobject.h>
+#include <vgui/KeyCode.h>
+#include <vgui_controls/Tooltip.h>
+#include <vgui_controls/CheckButton.h>
+
+wchar_t* LocalizeNumberWithToken( const char* pszLocToken, int nValue );
+
+//-----------------------------------------------------------------------------
+// Purpose: Xbox-specific panel that displays button icons text labels
+//-----------------------------------------------------------------------------
+class CTFFooter : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFFooter, vgui::EditablePanel );
+
+public:
+ CTFFooter( Panel *parent, const char *panelName );
+ virtual ~CTFFooter();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *pResourceData );
+ virtual void Paint( void );
+ virtual void PaintBackground( void );
+
+ void ShowButtonLabel( const char *name, bool show = true );
+ void AddNewButtonLabel( const char *name, const char *text, const char *icon );
+ void ClearButtons();
+
+private:
+ struct FooterButton_t
+ {
+ bool bVisible;
+ char name[MAX_PATH];
+ wchar_t text[MAX_PATH];
+ wchar_t icon[3]; // icon can be one or two characters
+ };
+
+ CUtlVector< FooterButton_t* > m_Buttons;
+
+ bool m_bPaintBackground; // fill the background?
+ int m_nButtonGap; // space between buttons
+ int m_FooterTall; // height of the footer
+ int m_ButtonOffsetFromTop; // how far below the top the buttons should be drawn
+ int m_ButtonSeparator; // space between the button icon and text
+ int m_TextAdjust; // extra adjustment for the text (vertically)...text is centered on the button icon and then this value is applied
+ bool m_bCenterHorizontal; // center buttons horizontally?
+ int m_ButtonPinRight; // if not centered, this is the distance from the right margin that we use to start drawing buttons (right to left)
+
+ char m_szTextFont[64]; // font for the button text
+ char m_szButtonFont[64]; // font for the button icon
+ char m_szFGColor[64]; // foreground color (text)
+ char m_szBGColor[64]; // background color (fill color)
+
+ vgui::HFont m_hButtonFont;
+ vgui::HFont m_hTextFont;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Tooltip for the main menu. Isn't a panel, it just wraps the
+// show/hide/position handling for the embedded panel.
+//-----------------------------------------------------------------------------
+class CMainMenuToolTip : public vgui::BaseTooltip
+{
+ DECLARE_CLASS_SIMPLE( CMainMenuToolTip, vgui::BaseTooltip );
+public:
+ CMainMenuToolTip(vgui::Panel *parent, const char *text = NULL) : vgui::BaseTooltip( parent, text )
+ {
+ m_pEmbeddedPanel = NULL;
+ }
+ virtual ~CMainMenuToolTip() {}
+
+ virtual void SetText(const char *text);
+ const char *GetText() { return NULL; }
+
+ virtual void HideTooltip();
+ virtual void PerformLayout();
+
+ void SetEmbeddedPanel( vgui::EditablePanel *pPanel )
+ {
+ m_pEmbeddedPanel = pPanel;
+ }
+
+protected:
+ vgui::EditablePanel *m_pEmbeddedPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Simple TF-styled text tooltip
+//-----------------------------------------------------------------------------
+class CTFTextToolTip : public CMainMenuToolTip
+{
+ DECLARE_CLASS_SIMPLE( CTFTextToolTip, CMainMenuToolTip );
+public:
+ CTFTextToolTip(vgui::Panel *parent, const char *text = NULL) : CMainMenuToolTip( parent, text )
+ {
+ }
+ virtual void PerformLayout();
+ virtual void PositionWindow( vgui::Panel *pTipPanel );
+ virtual void SetText(const char *text)
+ {
+ _isDirty = true;
+ BaseClass::SetText( text );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Displays a TF specific list of options
+// This is essentially a TF-styled version of the GameUI Advanced Multiplayer Options Dialog
+//-----------------------------------------------------------------------------
+class CTFAdvancedOptionsDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFAdvancedOptionsDialog, vgui::EditablePanel );
+
+public:
+ CTFAdvancedOptionsDialog(vgui::Panel *parent);
+ ~CTFAdvancedOptionsDialog();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *pResourceData );
+
+ void Deploy( void );
+
+private:
+
+ void CreateControls();
+ void DestroyControls();
+ void GatherCurrentValues();
+ void SaveValues();
+
+ virtual void OnCommand( const char *command );
+ virtual void OnClose();
+ virtual void OnKeyCodeTyped(vgui::KeyCode code);
+ virtual void OnKeyCodePressed(vgui::KeyCode code);
+
+private:
+ CInfoDescription *m_pDescription;
+ mpcontrol_t *m_pList;
+ vgui::PanelListPanel *m_pListPanel;
+ CTFTextToolTip *m_pToolTip;
+ vgui::EditablePanel *m_pToolTipEmbeddedPanel;
+
+ CPanelAnimationVarAliasType( int, m_iControlW, "control_w", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iControlH, "control_h", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iSliderW, "slider_w", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iSliderH, "slider_h", "0", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Scrollable panel where you can define children within the .res file
+//-----------------------------------------------------------------------------
+class CExScrollingEditablePanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CExScrollingEditablePanel, vgui::EditablePanel );
+public:
+ CExScrollingEditablePanel( Panel *pParent, const char *pszName );
+ virtual ~CExScrollingEditablePanel();
+
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnSizeChanged( int newWide, int newTall ) OVERRIDE;
+
+ MESSAGE_FUNC( OnScrollBarSliderMoved, "ScrollBarSliderMoved" );
+ virtual void OnMouseWheeled( int delta ) OVERRIDE; // respond to mouse wheel events
+ void ResetScrollAmount() { m_nLastScrollValue = 0; m_pScrollBar->SetValue(0); }
+protected:
+
+ void ShiftChildren( int nDistance );
+
+ vgui::ScrollBar *m_pScrollBar;
+ int m_nLastScrollValue;
+ bool m_bUseMouseWheelToScroll;
+ CPanelAnimationVarAliasType( int, m_iScrollStep, "scroll_step", "10", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iBottomBuffer, "bottom_buffer", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_bRestrictWidth, "restrict_width", "1", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// An extension of CExScrollingEditablePanel where panels can be added to form
+// a list.
+//-----------------------------------------------------------------------------
+class CScrollableList : public CExScrollingEditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CScrollableList, CExScrollingEditablePanel );
+public:
+ CScrollableList( Panel* pParent, const char* pszName )
+ : CExScrollingEditablePanel( pParent, pszName )
+ {}
+
+ virtual ~CScrollableList();
+
+ virtual void PerformLayout() OVERRIDE;
+
+ void AddPanel( Panel* pPanel, int nGap );
+ void ClearAutoLayoutPanels();
+
+private:
+
+ struct LayoutInfo_t
+ {
+ Panel* m_pPanel;
+ int m_nGap;
+ };
+
+ CUtlVector< LayoutInfo_t > m_vecAutoLayoutPanels;
+};
+
+//-----------------------------------------------------------------------------
+// A checkbox where keyvalue data can be stored and retrieved (typically in OnCheckButtonChecked)
+// so that you don't need a pointer to the checkbox in order to determine WHICH
+// checkbox got checked.
+//-----------------------------------------------------------------------------
+class CExCheckButton : public vgui::CheckButton
+{
+ DECLARE_CLASS_SIMPLE( CExCheckButton, vgui::CheckButton );
+public:
+ CExCheckButton( Panel* pParent, const char* pszName )
+ : BaseClass( pParent, pszName, NULL )
+ , m_pKVData( NULL )
+ {}
+
+ virtual ~CExCheckButton()
+ {
+ if ( m_pKVData )
+ m_pKVData->deleteThis();
+ }
+
+ void SetData( KeyValues* pKVData )
+ {
+ if ( m_pKVData )
+ {
+ m_pKVData->deleteThis();
+ m_pKVData = NULL;
+ }
+
+ m_pKVData = pKVData;
+ }
+
+ KeyValues* GetData() const
+ {
+ return m_pKVData;
+ }
+
+private:
+ KeyValues *m_pKVData;
+};
+
+class CExpandablePanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CExpandablePanel, vgui::EditablePanel );
+public:
+ CExpandablePanel( Panel* pParent, const char* pszName );
+
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+
+ virtual void OnToggleCollapse( bool bIsExpanded ) {}
+
+ void SetCollapsed( bool bCollapsed );
+ void ToggleCollapse();
+ bool BIsExpanded() const { return m_bExpanded; }
+ void SetExpandedHeight( int nNewHeight );
+ float GetPercentAnimated() const;
+
+protected:
+
+ CPanelAnimationVarAliasType( float, m_flResizeTime, "resize_time", "0.4", "float" );
+ CPanelAnimationVarAliasType( int, m_nCollapsedHeight, "collapsed_height", "17", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_nExpandedHeight, "expanded_height", "50", "proportional_int" );
+
+private:
+
+ bool m_bExpanded;
+ float m_flAnimEndTime;
+};
+
+#endif // TF_CONTROLS_H
diff --git a/game/client/tf/vgui/tf_giveawayitempanel.cpp b/game/client/tf/vgui/tf_giveawayitempanel.cpp
new file mode 100644
index 0000000..e85bd06
--- /dev/null
+++ b/game/client/tf/vgui/tf_giveawayitempanel.cpp
@@ -0,0 +1,447 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "vgui/IInput.h"
+#include <vgui/IVGui.h>
+#include <vgui/IScheme.h>
+#include "tf_giveawayitempanel.h"
+#include "iclientmode.h"
+#include "baseviewport.h"
+#include "econ_entity.h"
+#include "c_tf_player.h"
+#include "gamestringpool.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/Label.h"
+#include "vgui_controls/Button.h"
+#include "econ_item_system.h"
+#include "ienginevgui.h"
+#include "achievementmgr.h"
+#include "fmtstr.h"
+#include "c_tf_playerresource.h"
+#include "tf_gamerules.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CGiveawayPlayerPanel::CGiveawayPlayerPanel( vgui::Panel *parent, const char *name ) : BaseClass(parent,name)
+{
+ m_pNameLabel = new vgui::Label( this, "name_label", "" );
+ m_pScoreLabel = new vgui::Label( this, "score_label", "" );
+
+ REGISTER_COLOR_AS_OVERRIDABLE( m_PlayerColorLocal, "fgcolor_local" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_PlayerColorOther, "fgcolor_other" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGiveawayPlayerPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGiveawayPlayerPanel::SetPlayer( CTFPlayer *pPlayer )
+{
+ m_iBonus = 0;
+
+ if ( pPlayer )
+ {
+ SetVisible( true );
+
+ if ( g_TF_PR )
+ {
+ int playerIndex = pPlayer->entindex();
+ const char *pszName = g_TF_PR->GetPlayerName( playerIndex );
+ m_iBonus = pPlayer->m_Shared.GetItemFindBonus();
+
+ SetDialogVariable( "playername", pszName );
+ SetDialogVariable( "playerscore", m_iBonus );
+ }
+
+ m_iPlayerIndex = pPlayer->entindex();
+
+ if ( pPlayer->IsLocalPlayer() )
+ {
+ m_pNameLabel->SetFgColor( m_PlayerColorLocal );
+ m_pScoreLabel->SetFgColor( m_PlayerColorLocal );
+ }
+ else
+ {
+ m_pNameLabel->SetFgColor( m_PlayerColorOther );
+ m_pScoreLabel->SetFgColor( m_PlayerColorOther );
+ }
+ }
+ else
+ {
+ SetVisible( false );
+ m_iPlayerIndex = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGiveawayPlayerPanel::SpinBonus( void )
+{
+ SetDialogVariable( "playerscore", RandomInt( PLAYER_ROLL_MIN, PLAYER_ROLL_MAX ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGiveawayPlayerPanel::LockBonus( int iRoll )
+{
+ m_iRoll = iRoll;
+ SetDialogVariable( "playerscore", m_iBonus + m_iRoll );
+}
+
+//===========================================================================================================================
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGiveawayItemPanel::CTFGiveawayItemPanel( IViewPort *pViewPort ) : Frame( NULL, PANEL_GIVEAWAY_ITEM )
+{
+ m_pViewPort = pViewPort;
+
+ // load the new scheme early!!
+ SetScheme( "ClientScheme" );
+
+ SetTitleBarVisible( false );
+ SetMinimizeButtonVisible( false );
+ SetMaximizeButtonVisible( false );
+ SetCloseButtonVisible( false );
+ SetSizeable( false );
+ SetMoveable( false );
+ SetProportional( true );
+ SetVisible( false );
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+
+ m_pModelPanel = new CItemModelPanel( this, "item_panel" );
+ m_pPlayerListPanelKVs = NULL;
+
+ m_iNumActivePlayers = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGiveawayItemPanel::~CTFGiveawayItemPanel( void )
+{
+ if ( m_pPlayerListPanelKVs )
+ {
+ m_pPlayerListPanelKVs->deleteThis();
+ m_pPlayerListPanelKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/GiveawayItemPanel.res" );
+
+ for ( int i = 0; i < m_aPlayerList.Count(); i++ )
+ {
+ m_aPlayerList[i]->ApplySettings( m_pPlayerListPanelKVs );
+ m_aPlayerList[i]->SetBorder( pScheme->GetBorder("EconItemBorder") );
+ m_aPlayerList[i]->InvalidateLayout();
+ }
+
+ m_pModelPanel->SetNoItemText( "#Item_Giveaway_NoItem" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "playerlist_panel_kvs" );
+ if ( pItemKV )
+ {
+ if ( m_pPlayerListPanelKVs )
+ {
+ m_pPlayerListPanelKVs->deleteThis();
+ }
+ m_pPlayerListPanelKVs = new KeyValues("playerlist_panel_kvs");
+ pItemKV->CopySubkeys( m_pPlayerListPanelKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ int iPosition = 0;
+ for ( int i = 0; i < m_aPlayerList.Count(); i++ )
+ {
+ if ( !m_aPlayerList[i]->IsVisible() )
+ continue;
+
+ int iCenter = GetWide() * 0.5;
+ int iXPos = iCenter;
+ int iYPos = 0;
+ if ( iPosition < (m_iNumActivePlayers * 0.5) )
+ {
+ iXPos -= m_aPlayerList[i]->GetWide() + m_iPlayerXOffset;
+ iYPos = m_iPlayerYPos + iPosition * m_aPlayerList[i]->GetTall();
+ }
+ else
+ {
+ iXPos += m_iPlayerXOffset;
+ iYPos = m_iPlayerYPos + (iPosition - ceil(m_iNumActivePlayers * 0.5)) * m_aPlayerList[i]->GetTall();
+ }
+ m_aPlayerList[i]->SetPos( iXPos, iYPos );
+
+ iPosition++;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::ShowPanel(bool bShow)
+{
+ if ( bShow )
+ {
+ SetItem( NULL );
+ m_bBuiltPlayerList = false;
+ m_flNextRollStart = 0;
+ m_iRollingForPlayer = 0;
+ m_iNumActivePlayers = 0;
+ }
+
+ SetMouseInputEnabled( bShow );
+ SetVisible( bShow );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ ShowPanel( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "vguicancel" ) )
+ {
+ ShowPanel( false );
+
+ // If we're connected to a game server, we also close the game UI.
+ if ( engine->IsInGame() )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::Update( void )
+{
+ // First, wait for the player list to get built
+ if ( !m_bBuiltPlayerList )
+ {
+ BuildPlayerList();
+ if ( !m_bBuiltPlayerList )
+ return;
+ }
+
+ // Then wait for the server to send us the item details
+ if ( !m_pModelPanel->HasItem() )
+ {
+ if ( TFGameRules() )
+ {
+ CBonusRoundLogic *pLogic = TFGameRules()->GetBonusLogic();
+ if ( pLogic )
+ {
+ m_pModelPanel->SetItem( pLogic->GetBonusItem() );
+ }
+ }
+
+ if ( !m_pModelPanel->HasItem() )
+ return;
+ }
+
+ // Then start rolling through the players and locking in their bonuses
+ if ( m_iRollingForPlayer < m_iNumActivePlayers )
+ {
+ // Spin all the numbers & lock them in one by one
+ for ( int i = 0; i < m_aPlayerList.Count(); i++ )
+ {
+ if ( m_iRollingForPlayer < i )
+ {
+ // Haven't got to this player yet, so spin their bonus.
+ m_aPlayerList[i]->SpinBonus();
+ continue;
+ }
+
+ if ( i == m_iRollingForPlayer )
+ {
+ if ( gpGlobals->curtime > m_flNextRollStart )
+ {
+ // Lock in this player's bonus and move on to the next one
+ CBonusRoundLogic *pLogic = TFGameRules()->GetBonusLogic();
+ if ( pLogic )
+ {
+ m_aPlayerList[i]->LockBonus( pLogic->GetPlayerBonusRoll( m_aPlayerList[i]->GetPlayerIndex() ) );
+ }
+
+ m_iRollingForPlayer++;
+ m_flNextRollStart = gpGlobals->curtime + 0.4;
+ }
+ else
+ {
+ m_aPlayerList[i]->SpinBonus();
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::BuildPlayerList( void )
+{
+ CBonusRoundLogic *pLogic = TFGameRules()->GetBonusLogic();
+ if ( !pLogic )
+ return;
+
+ m_bBuiltPlayerList = true;
+
+ pLogic->BuildBonusPlayerList();
+
+ for ( int i = 0; i < m_aPlayerList.Count(); i++ )
+ {
+ m_aPlayerList[i]->SetPlayer(NULL);
+ }
+
+ m_iNumActivePlayers = 0;
+ for( int playerIndex = 0; playerIndex < pLogic->GetNumBonusPlayers(); playerIndex++ )
+ {
+ C_TFPlayer *pPlayer = pLogic->GetBonusPlayer(playerIndex);
+ if ( !pPlayer )
+ continue;
+
+ CGiveawayPlayerPanel *pPlayerPanel;
+ if ( m_iNumActivePlayers < m_aPlayerList.Count() )
+ {
+ pPlayerPanel = m_aPlayerList[m_iNumActivePlayers];
+ }
+ else
+ {
+ const char *pszCommand = VarArgs("playerpanel%d",m_iNumActivePlayers);
+ pPlayerPanel = new CGiveawayPlayerPanel( this, pszCommand );
+ if ( m_pPlayerListPanelKVs )
+ {
+ pPlayerPanel->ApplySettings( m_pPlayerListPanelKVs );
+ }
+ pPlayerPanel->MakeReadyForUse();
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ pPlayerPanel->SetBorder( pScheme->GetBorder("EconItemBorder") );
+
+ m_aPlayerList.AddToTail( pPlayerPanel );
+ }
+
+ pPlayerPanel->SetPlayer( pPlayer );
+
+ m_iNumActivePlayers++;
+ }
+
+ // Start the animation
+ m_flNextRollStart = gpGlobals->curtime + 1.0;
+ m_iRollingForPlayer = 0;
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGiveawayItemPanel::SetItem( CEconItemView *pItem )
+{
+ m_pModelPanel->SetItem( pItem );
+}
+
+static vgui::DHANDLE<CTFGiveawayItemPanel> g_GiveawayItemPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGiveawayItemPanel *OpenGiveawayItemPanel( CEconItemView *pItem )
+{
+ CTFGiveawayItemPanel *pPanel = (CTFGiveawayItemPanel*)gViewPortInterface->FindPanelByName( PANEL_GIVEAWAY_ITEM );
+ if ( pPanel )
+ {
+ pPanel->InvalidateLayout( false, true );
+ pPanel->SetItem( pItem );
+ gViewPortInterface->ShowPanel( pPanel, true );
+ }
+ return pPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Test_GiveawayItemPanel( const CCommand &args )
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalPlayer )
+ return;
+
+ CEconItemView *pScriptCreatedItem = NULL;
+
+ bool bAllItems = (args.ArgC() <= 1);
+ for ( int i = bAllItems ? 0 : clamp( atoi(args[1]), 0, 2 ); i <= 2; i++ )
+ {
+ CEconEntity *pItem = dynamic_cast<CEconEntity *>( pLocalPlayer->Weapon_GetWeaponByType( i ) );
+ if ( !pItem )
+ continue;
+
+ pScriptCreatedItem = pItem->GetAttributeContainer()->GetItem();
+ break;
+ }
+
+ if ( pScriptCreatedItem )
+ {
+ OpenGiveawayItemPanel( pScriptCreatedItem );
+ }
+}
+
+ConCommand test_giveawayitem( "test_giveawayitem", Test_GiveawayItemPanel, "Debugging tool to test the item giveaway panel. Usage: test_giveawayitem <weapon name>\n <weapon id>: 0 = primary, 1 = secondary, 2 = melee.", FCVAR_CHEAT );
diff --git a/game/client/tf/vgui/tf_giveawayitempanel.h b/game/client/tf/vgui/tf_giveawayitempanel.h
new file mode 100644
index 0000000..cd8b106
--- /dev/null
+++ b/game/client/tf/vgui/tf_giveawayitempanel.h
@@ -0,0 +1,108 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_GIVEAWAYITEMPANEL_H
+#define TF_GIVEAWAYITEMPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/Frame.h>
+#include <game/client/iviewport.h>
+#include "GameEventListener.h"
+#include "basemodel_panel.h"
+#include "basemodelpanel.h"
+#include "tf_shareddefs.h"
+#include "econ_item_inventory.h"
+#include "econ_item_view.h"
+#include "item_model_panel.h"
+#include "c_tf_player.h"
+
+class CEconItemView;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CGiveawayPlayerPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CGiveawayPlayerPanel, vgui::EditablePanel );
+public:
+ CGiveawayPlayerPanel( vgui::Panel *parent, const char *name );
+
+ virtual void PerformLayout( void );
+
+ void SetPlayer( CTFPlayer *pPlayer );
+ int GetPlayerIndex( void ) { return m_iPlayerIndex; }
+ int GetBonus( void ) { return m_iBonus; }
+ void SpinBonus( void );
+ void LockBonus( int iRoll );
+
+private:
+ vgui::Label *m_pNameLabel;
+ vgui::Label *m_pScoreLabel;
+ Color m_PlayerColorLocal;
+ Color m_PlayerColorOther;
+ int m_iBonus;
+ int m_iRoll;
+ int m_iPlayerIndex;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFGiveawayItemPanel : public vgui::Frame, public IViewPortPanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CTFGiveawayItemPanel, vgui::Frame );
+public:
+ CTFGiveawayItemPanel( IViewPort *pViewPort );
+ ~CTFGiveawayItemPanel( void );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ void SetItem( CEconItemView *pItem );
+ void BuildPlayerList( void );
+
+ // IViewPortPanel overrides
+ virtual const char *GetName( void ){ return PANEL_GIVEAWAY_ITEM; }
+ virtual void SetData( KeyValues *data ) { return; }
+ virtual void Reset(){ Update(); }
+ virtual void Update();
+ virtual void ShowPanel( bool bShow );
+ virtual bool NeedsUpdate( void ){ return true; }
+ virtual bool HasInputElements( void ){ return true; }
+
+ // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui
+ vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); }
+ virtual bool IsVisible(){ return BaseClass::IsVisible(); }
+ virtual void SetParent( vgui::VPANEL parent ){ BaseClass::SetParent( parent ); }
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_MENUCONTROLS; }
+
+private:
+ IViewPort *m_pViewPort;
+ CItemModelPanel *m_pModelPanel;
+ CUtlVector< CGiveawayPlayerPanel * > m_aPlayerList;
+ KeyValues *m_pPlayerListPanelKVs;
+ int m_iNumActivePlayers;
+
+ // Animation
+ bool m_bBuiltPlayerList;
+ float m_flNextRollStart;
+ int m_iRollingForPlayer;
+
+ CPanelAnimationVarAliasType( int, m_iPlayerYPos, "player_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iPlayerXOffset, "player_xoffset", "0", "proportional_int" );
+};
+
+CTFGiveawayItemPanel *OpenGiveawayItemPanel( CEconItemView *pItem );
+
+#endif // TF_GIVEAWAYITEMPANEL_H
diff --git a/game/client/tf/vgui/tf_imagepanel.cpp b/game/client/tf/vgui/tf_imagepanel.cpp
new file mode 100644
index 0000000..28ba950
--- /dev/null
+++ b/game/client/tf/vgui/tf_imagepanel.cpp
@@ -0,0 +1,87 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include <KeyValues.h>
+#include <vgui/IScheme.h>
+#include <vgui/ISurface.h>
+#include <vgui/ISystem.h>
+#include <vgui_controls/AnimationController.h>
+#include <vgui_controls/EditablePanel.h>
+#include <vgui/ISurface.h>
+#include <vgui/IImage.h>
+#include <vgui_controls/Label.h>
+
+#include "tf_imagepanel.h"
+#include "c_tf_player.h"
+
+using namespace vgui;
+
+DECLARE_BUILD_FACTORY( CTFImagePanel );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFImagePanel::CTFImagePanel( Panel *parent, const char *name ) : ScalableImagePanel( parent, name )
+{
+ for ( int i = 0; i < TF_TEAM_COUNT; i++ )
+ {
+ m_szTeamBG[i][0] = '\0';
+ }
+
+ C_TFPlayer *pPlayer = ToTFPlayer( C_BasePlayer::GetLocalPlayer() );
+ m_iBGTeam = pPlayer ? pPlayer->GetTeamNumber() : TEAM_UNASSIGNED;
+
+ ListenForGameEvent( "localplayer_changeteam" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFImagePanel::ApplySettings( KeyValues *inResourceData )
+{
+ for ( int i = 0; i < TF_TEAM_COUNT; i++ )
+ {
+ Q_strncpy( m_szTeamBG[i], inResourceData->GetString( VarArgs("teambg_%d", i), "" ), sizeof( m_szTeamBG[i] ) );
+
+ if ( m_szTeamBG[i] && m_szTeamBG[i][0] )
+ {
+ PrecacheMaterial( VarArgs( "vgui/%s", m_szTeamBG[i] ) );
+ }
+ }
+
+ BaseClass::ApplySettings( inResourceData );
+
+ UpdateBGImage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFImagePanel::UpdateBGImage( void )
+{
+ if ( m_iBGTeam >= 0 && m_iBGTeam < TF_TEAM_COUNT )
+ {
+ if ( m_szTeamBG[m_iBGTeam] && m_szTeamBG[m_iBGTeam][0] )
+ {
+ SetImage( m_szTeamBG[m_iBGTeam] );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFImagePanel::FireGameEvent( IGameEvent * event )
+{
+ if ( FStrEq( "localplayer_changeteam", event->GetName() ) )
+ {
+ C_TFPlayer *pPlayer = ToTFPlayer( C_BasePlayer::GetLocalPlayer() );
+ m_iBGTeam = pPlayer ? pPlayer->GetTeamNumber() : TEAM_UNASSIGNED;
+ UpdateBGImage();
+ }
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_imagepanel.h b/game/client/tf/vgui/tf_imagepanel.h
new file mode 100644
index 0000000..ffa842f
--- /dev/null
+++ b/game/client/tf/vgui/tf_imagepanel.h
@@ -0,0 +1,41 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_IMAGEPANEL_H
+#define TF_IMAGEPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_shareddefs.h"
+#include <vgui/IScheme.h>
+#include <vgui_controls/ScalableImagePanel.h>
+#include "GameEventListener.h"
+
+#define MAX_BG_LENGTH 128
+
+class CTFImagePanel : public vgui::ScalableImagePanel, public CGameEventListener
+{
+public:
+ DECLARE_CLASS_SIMPLE( CTFImagePanel, vgui::ScalableImagePanel );
+
+ CTFImagePanel( vgui::Panel *parent, const char *name );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ void UpdateBGImage( void );
+ void SetBGTeam( int iTeam ) { m_iBGTeam = iTeam; }
+
+public: // IGameEventListener Interface
+ virtual void FireGameEvent( IGameEvent * event );
+
+public:
+ char m_szTeamBG[TF_TEAM_COUNT][MAX_BG_LENGTH];
+ int m_iBGTeam;
+};
+
+
+#endif // TF_IMAGEPANEL_H
diff --git a/game/client/tf/vgui/tf_intromenu.cpp b/game/client/tf/vgui/tf_intromenu.cpp
new file mode 100644
index 0000000..5cf7670
--- /dev/null
+++ b/game/client/tf/vgui/tf_intromenu.cpp
@@ -0,0 +1,703 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include <KeyValues.h>
+#include <vgui/IVGui.h>
+#include <vgui/ISurface.h>
+#include <filesystem.h>
+#include <vgui_controls/AnimationController.h>
+#include "iclientmode.h"
+#include "clientmode_shared.h"
+#include "shareddefs.h"
+#include "tf_shareddefs.h"
+#include "tf_controls.h"
+#include "tf_gamerules.h"
+#ifdef WIN32
+#include "winerror.h"
+#endif
+#include "ixboxsystem.h"
+#include "intromenu.h"
+#include "tf_intromenu.h"
+#include "inputsystem/iinputsystem.h"
+
+// used to determine the action the intro menu should take when OnTick handles a think for us
+enum
+{
+ INTRO_NONE,
+ INTRO_STARTVIDEO,
+ INTRO_BACK,
+ INTRO_CONTINUE,
+};
+
+using namespace vgui;
+
+// sort function for the list of captions that we're going to show
+int CaptionsSort( CVideoCaption* const *p1, CVideoCaption* const *p2 )
+{
+ // check the start time
+ if ( (*p2)->m_flStartTime < (*p1)->m_flStartTime )
+ {
+ return 1;
+ }
+
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFIntroMenu::CTFIntroMenu( IViewPort *pViewPort ) : BaseClass( pViewPort )
+{
+ m_pVideo = new CTFVideoPanel( this, "VideoPanel" );
+ m_pModel = new CModelPanel( this, "MenuBG" );
+ m_pCaptionLabel = new CExLabel( this, "VideoCaption", "" );
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+#else
+ m_pBack = new CExButton( this, "Back", "" );
+ m_pOK = new CExButton( this, "Skip", "" );
+ m_pReplayVideo = new CExButton( this, "ReplayVideo", "" );
+ m_pContinue = new CExButton( this, "Continue", "" );
+#endif
+
+ m_iCurrentCaption = 0;
+ m_flVideoStartTime = 0;
+
+ m_flActionThink = -1;
+ m_iAction = INTRO_NONE;
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Flag for weather or not we're playing an in game video.
+ //=============================================================================
+ m_bPlayingInGameVideo = false;
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CTFIntroMenu::~CTFIntroMenu()
+{
+ m_Captions.PurgeAndDeleteElements();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/IntroMenu_SC.res" );
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/IntroMenu.res" );
+ SetMouseInputEnabled( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::SetNextThink( float flActionThink, int iAction )
+{
+ m_flActionThink = flActionThink;
+ m_iAction = iAction;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::OnTick()
+{
+ // @note Tom Bui: (yuck)
+ // in training, never show the back button
+ // we do this late, because there's a race condition for when IsInTraining() will return true
+ if ( m_pBack->IsVisible() && TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ m_pBack->SetVisible(false);
+ }
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Used to play a movie during a map. For training videos.
+ //=============================================================================
+ if ( PendingInGameVideo() && !BaseClass::IsVisible() )
+ {
+ m_pViewPort->ShowPanel( this, true );
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ // do we have anything special to do?
+ else if ( m_flActionThink > 0 && m_flActionThink < gpGlobals->curtime )
+ {
+ if ( m_iAction == INTRO_STARTVIDEO )
+ {
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Pulled start video into a separate function.
+ //=============================================================================
+ StartVideo();
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+ }
+ else if ( m_iAction == INTRO_BACK )
+ {
+ m_pViewPort->ShowPanel( this, false );
+ m_pViewPort->ShowPanel( PANEL_MAPINFO, true );
+ }
+ else if ( m_iAction == INTRO_CONTINUE )
+ {
+ m_pViewPort->ShowPanel( this, false );
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Used for the client to tell the server that we're whatching a movie or not
+ //=============================================================================
+ tf_training_client_message.SetValue( "" );
+ tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_NONE );
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED )
+ {
+ if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
+ {
+ m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true );
+ }
+ else if ( TFGameRules()->IsMannVsMachineMode() || TFGameRules()->IsCompetitiveMode() )
+ {
+ engine->ClientCmd( "autoteam" );
+ }
+ else
+ {
+ m_pViewPort->ShowPanel( PANEL_TEAM, true );
+ }
+ }
+ else
+ {
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ // only open the class menu if they're not on team Spectator and they haven't already picked a class
+ if ( pPlayer &&
+ ( GetLocalPlayerTeam() != TEAM_SPECTATOR ) &&
+ ( pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED ) )
+ {
+ if ( tf_arena_force_class.GetBool() == false )
+ {
+ switch( GetLocalPlayerTeam() )
+ {
+ case TF_TEAM_RED:
+ m_pViewPort->ShowPanel( PANEL_CLASS_RED, true );
+ break;
+
+ case TF_TEAM_BLUE:
+ m_pViewPort->ShowPanel( PANEL_CLASS_BLUE, true );
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // reset our think
+ SetNextThink( -1, INTRO_NONE );
+ }
+
+ // check if we need to update our captions
+ if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() )
+ {
+ UpdateCaptions();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::OnThink()
+{
+ //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
+ }
+
+ BaseClass::OnThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFIntroMenu::LoadCaptions( void )
+{
+ bool bSuccess = false;
+
+ // clear any current captions
+ m_Captions.PurgeAndDeleteElements();
+ m_iCurrentCaption = 0;
+
+ if ( m_pCaptionLabel )
+ {
+ const char *szVideoFileName = GetVideoFileName( false );
+ KeyValues *kvCaptions = NULL;
+ char strFullpath[MAX_PATH];
+ if ( szVideoFileName != NULL )
+ {
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] The video may now be either a map video or an in game video.
+ // Made a function to decide which video name to give back.
+ //=============================================================================
+ Q_strncpy( strFullpath, szVideoFileName, MAX_PATH ); // Assume we must play out of the media directory
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ Q_strncat( strFullpath, ".res", MAX_PATH ); // Assume we're a .res extension type
+
+ if ( g_pFullFileSystem->FileExists( strFullpath ) )
+ {
+ kvCaptions = new KeyValues( strFullpath );
+
+ if ( kvCaptions )
+ {
+ if ( kvCaptions->LoadFromFile( g_pFullFileSystem, strFullpath ) )
+ {
+ for ( KeyValues *pData = kvCaptions->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
+ {
+ CVideoCaption *pCaption = new CVideoCaption;
+ if ( pCaption )
+ {
+ pCaption->m_pszString = ReadAndAllocStringValue( pData, "string" );
+ pCaption->m_flStartTime = pData->GetFloat( "start", 0.0 );
+ pCaption->m_flDisplayTime = pData->GetFloat( "length", 3.0 );
+
+ m_Captions.AddToTail( pCaption );
+
+ // we have at least one caption to show
+ bSuccess = true;
+ }
+ }
+ }
+
+ kvCaptions->deleteThis();
+ }
+ }
+ }
+ }
+
+ if ( bSuccess )
+ {
+ // sort the captions so we show them in the correct order (they're not necessarily in order in the .res file)
+ m_Captions.Sort( CaptionsSort );
+ }
+
+ return bSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::UpdateCaptions( void )
+{
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Timing should be realtime when playing in game becase the curtime is paused.
+ //=============================================================================
+ float testTime = m_bPlayingInGameVideo ? gpGlobals->realtime : gpGlobals->curtime;
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() && ( m_Captions.Count() > 0 ) )
+ {
+ CVideoCaption *pCaption = m_Captions[m_iCurrentCaption];
+
+ if ( pCaption )
+ {
+ if ( ( pCaption->m_flCaptionStart >= 0 ) && ( pCaption->m_flCaptionStart + pCaption->m_flDisplayTime < testTime ) )
+ {
+ // fade out the caption
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "VideoCaptionFadeOut" );
+
+ // move to the next caption
+ m_iCurrentCaption++;
+
+ if ( !m_Captions.IsValidIndex( m_iCurrentCaption ) )
+ {
+ // we're done showing captions
+ m_pCaptionLabel->SetVisible( false );
+ }
+ }
+ // is it time to show the caption?
+ else if ( m_flVideoStartTime + pCaption->m_flStartTime < testTime )
+ {
+ // have we already started this video?
+ if ( pCaption->m_flCaptionStart < 0 )
+ {
+ m_pCaptionLabel->SetText( pCaption->m_pszString );
+ pCaption->m_flCaptionStart = testTime;
+
+ // fade in the next caption
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "VideoCaptionFadeIn" );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::ShowPanel( bool bShow )
+{
+
+ //=============================================================================
+ // HPE_BEGIN:
+ // [msmith] Don't show the back button when in training. You can only skip intro
+ // movies.
+ //=============================================================================
+ m_pBack->SetVisible(true);
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ m_pBack->SetVisible( false );
+ if ( PendingInGameVideo() == false )
+ {
+ VideoSystem_t playbackSystem = VideoSystem::NONE;
+ char resolvedFile[MAX_PATH];
+ if ( g_pVideo != NULL && g_pVideo->LocatePlayableVideoFile( GetVideoFileName(), "GAME", &playbackSystem, resolvedFile, sizeof(resolvedFile) ) != VideoResult::SUCCESS )
+ {
+ //If we have no movie, no need to show the intro screen on a training mission.
+ bShow = false;
+ }
+ }
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+
+ if ( BaseClass::IsVisible() == bShow )
+ return;
+
+ // reset our think
+ SetNextThink( -1, INTRO_NONE );
+
+ if ( bShow )
+ {
+ InvalidateLayout( true, true );
+ Activate();
+
+ if ( m_pVideo )
+ {
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Pulled shutting down the video into a separate function.
+ // If we're showing an in game video, we need to enable pausing so that
+ // we can pause the game during the video.
+ // If we're showing an intro training movie, we also need to tell the server that
+ // we're whatching the intro movie so that the round does not start until it's over.
+ // If we are watching an in game video, we do NOT send a message for that because
+ // tf_training_client_message will contain the name of the video we're watching.
+ //=============================================================================
+ ShutdownVideo();
+ SetNextThink( gpGlobals->curtime + m_pVideo->GetStartDelay(), INTRO_STARTVIDEO );
+
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ if ( PendingInGameVideo() )
+ {
+ engine->ClientCmd( "sv_pausable 1" );
+ }
+ else
+ {
+ tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_WATCHING_INTRO_MOVIE );
+ }
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ }
+
+ if ( m_pModel )
+ {
+ m_pModel->SetPanelDirty();
+ }
+ }
+ else
+ {
+ Shutdown();
+
+ SetVisible( false );
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] We must disable the ability to pause. If we don't, it looks like
+ // some other function in TF2 causes the entire game to pause if sv_pausable is enabled.
+ //=============================================================================
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ engine->ClientCmd( "sv_pausable 0" );
+ }
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::OnIntroFinished( void )
+{
+ // in training we want to give the user the ability to replay the movie
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ m_pReplayVideo->SetVisible( true );
+ m_pContinue->SetVisible( true );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "IntroMovieContinueBlink" );
+ m_pOK->SetVisible( false );
+ }
+ else
+ {
+ float flTime = gpGlobals->curtime;
+
+ if ( m_pModel && m_pModel->SetSequence( "UpSlow" ) )
+ {
+ // wait for the model sequence to finish before going to the next menu
+ flTime = gpGlobals->curtime + m_pVideo->GetEndDelay();
+ }
+
+ Shutdown();
+
+ SetNextThink( flTime, INTRO_CONTINUE );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::OnCommand( const char *command )
+{
+ if ( !Q_strcmp( command, "back" ) )
+ {
+ float flTime = gpGlobals->curtime;
+
+ Shutdown();
+
+ // try to play the screenup sequence
+ if ( m_pModel && m_pModel->SetSequence( "Up" ) )
+ {
+ flTime = gpGlobals->curtime + 0.35f;
+ }
+
+ // wait for the model sequence to finish before going back to the mapinfo menu
+ SetNextThink( flTime, INTRO_BACK );
+ }
+ else if ( !Q_strcmp( command, "skip" ) )
+ {
+ Shutdown();
+
+ // continue right now
+ SetNextThink( gpGlobals->curtime, INTRO_CONTINUE );
+ }
+ else if ( !Q_strcmp( command, "replayVideo" ) )
+ {
+ ShutdownVideo();
+ SetNextThink( gpGlobals->curtime, INTRO_STARTVIDEO );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::OnKeyCodePressed( KeyCode code )
+{
+ if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
+ {
+ OnCommand( "skip" );
+ }
+ else if ( code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B )
+ {
+ OnCommand( "back" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFIntroMenu::Shutdown( void )
+{
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Refactored the shutdown video logic into a containing function.
+ //=============================================================================
+ ShutdownVideo();
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() )
+ {
+ m_pCaptionLabel->SetVisible( false );
+ }
+
+ m_iCurrentCaption = 0;
+ m_flVideoStartTime = 0;
+
+}
+
+
+
+
+//=============================================================================
+// HPE_BEGIN
+// [msmith] New helper functions
+//=============================================================================
+void CTFIntroMenu::ShutdownVideo()
+{
+ if ( m_pVideo )
+ {
+ m_pVideo->Shutdown(); // make sure we're not currently running
+ }
+
+ //Make sure we unpause the game if it was paused from an in game play of a video.
+ if ( m_bPlayingInGameVideo )
+ {
+ UnpauseGame();
+ }
+
+ m_bPlayingInGameVideo = false;
+}
+
+bool CTFIntroMenu::PendingInGameVideo( void )
+{
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ //If the message is a string, it's a video name.
+ return strlen( tf_training_client_message.GetString() ) > 3;
+ }
+
+ return false;
+}
+
+const char *CTFIntroMenu::GetVideoFileName( bool withExtension )
+{
+ if ( PendingInGameVideo() )
+ {
+ return TFGameRules()->FormatVideoName( tf_training_client_message.GetString(), withExtension );
+ }
+
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ ConVarRef training_map_video("training_map_video");
+ if ( strlen( training_map_video.GetString() ) > 3 )
+ {
+ return TFGameRules()->FormatVideoName( training_map_video.GetString(), withExtension );
+ }
+ }
+
+ return TFGameRules()->GetVideoFileForMap( withExtension );
+}
+
+void CTFIntroMenu::StartVideo()
+{
+ m_pOK->SetVisible( true );
+ m_pReplayVideo->SetVisible( false );
+ m_pContinue->SetVisible( false );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "IntroMovieContinueBlinkStop" );
+ if ( m_pVideo )
+ {
+ // turn on the captions if we have them
+ if ( LoadCaptions() )
+ {
+ if ( m_pCaptionLabel && !m_pCaptionLabel->IsVisible() )
+ {
+ m_pCaptionLabel->SetText( " " );
+ m_pCaptionLabel->SetVisible( true );
+ //Make sure the label is fully faded in when starting to play.
+ //It could have been faded out from a prior animation event form an animation effect in a previous video instance.
+ m_pCaptionLabel->SetAlpha( 255 );
+ }
+ }
+ else
+ {
+ if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() )
+ {
+ m_pCaptionLabel->SetVisible( false );
+ }
+ }
+
+ m_pVideo->Activate();
+
+ if ( PendingInGameVideo() )
+ {
+ m_pVideo->BeginPlayback( GetVideoFileName() );
+ PauseGame();
+ m_bPlayingInGameVideo = true;
+
+ //Since we have started playing the video, we can reset the message string to empty.
+ tf_training_client_message.SetValue( "" );
+ }
+ else
+ {
+ m_pVideo->BeginPlayback( GetVideoFileName() );
+ }
+
+ m_pVideo->MoveToFront();
+
+ m_flVideoStartTime = m_bPlayingInGameVideo ? gpGlobals->realtime : gpGlobals->curtime;
+ }
+}
+
+void CTFIntroMenu::UnpauseGame( void )
+{
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ engine->ClientCmd( "unpause" );
+ }
+}
+
+void CTFIntroMenu::PauseGame( void )
+{
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ engine->ClientCmd( "pause" );
+ }
+}
+//=============================================================================
+// HPE_END
+//=============================================================================
diff --git a/game/client/tf/vgui/tf_intromenu.h b/game/client/tf/vgui/tf_intromenu.h
new file mode 100644
index 0000000..b9f2cf2
--- /dev/null
+++ b/game/client/tf/vgui/tf_intromenu.h
@@ -0,0 +1,128 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_INTROMENU_H
+#define TF_INTROMENU_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_vgui_video.h"
+#include "basemodelpanel.h"
+
+#define MAX_CAPTION_LENGTH 256
+
+class CVideoCaption
+{
+public:
+ CVideoCaption()
+ {
+ m_pszString = NULL;
+ m_flStartTime = 0;
+ m_flDisplayTime = 0;
+ m_flCaptionStart = -1;
+ }
+
+ ~CVideoCaption()
+ {
+ if ( m_pszString && m_pszString[0] )
+ {
+ delete [] m_pszString;
+ m_pszString = NULL;
+ }
+ }
+
+ const char *m_pszString; // the string to display (can be a localized # string)
+ float m_flStartTime; // the offset from the beginning of the video when we should show this caption
+ float m_flDisplayTime; // the length of time the string should be displayed once it's shown
+ float m_flCaptionStart; // the time when the caption is shown (so we know when to turn it off
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: displays the Intro menu
+//-----------------------------------------------------------------------------
+
+class CTFIntroMenu : public CIntroMenu
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFIntroMenu, CIntroMenu );
+
+public:
+ CTFIntroMenu( IViewPort *pViewPort );
+ ~CTFIntroMenu();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ShowPanel( bool bShow );
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodePressed( KeyCode code );
+
+ virtual void OnTick() OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Some refactoring.
+ //=============================================================================
+ void StartVideo();
+ void ShutdownVideo();
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ MESSAGE_FUNC( OnIntroFinished, "IntroFinished" );
+
+private:
+ void SetNextThink( float flActionThink, int iAction );
+ void Shutdown( void );
+ bool LoadCaptions( void );
+ void UpdateCaptions( void );
+
+
+
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Added support for in game videos.
+ //=============================================================================
+ bool PendingInGameVideo( void );
+ const char *GetVideoFileName( bool withExtension = true );
+ void UnpauseGame( void );
+ void PauseGame( void );
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+
+ CTFVideoPanel *m_pVideo;
+ CModelPanel *m_pModel;
+ CExLabel *m_pCaptionLabel;
+
+#ifdef _X360
+ CTFFooter *m_pFooter;
+#else
+ CExButton *m_pBack;
+ CExButton *m_pOK;
+ CExButton *m_pReplayVideo;
+ CExButton *m_pContinue;
+#endif
+
+ float m_flActionThink;
+ int m_iAction;
+
+ CUtlVector< CVideoCaption* > m_Captions;
+ int m_iCurrentCaption;
+ float m_flVideoStartTime;
+ //=============================================================================
+ // HPE_BEGIN
+ // [msmith] Added support for in game videos.
+ //=============================================================================
+ bool m_bPlayingInGameVideo;
+ //=============================================================================
+ // HPE_END
+ //=============================================================================
+};
+
+
+#endif // TF_INTROMENU_H
diff --git a/game/client/tf/vgui/tf_item_card_panel.cpp b/game/client/tf/vgui/tf_item_card_panel.cpp
new file mode 100644
index 0000000..0852db4
--- /dev/null
+++ b/game/client/tf/vgui/tf_item_card_panel.cpp
@@ -0,0 +1,767 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_item_card_panel.h"
+#include "econ_item_description.h"
+#include "vgui_controls/TextImage.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "navigationpanel.h"
+#include "IconPanel.h"
+#include "vgui_controls/ScrollBar.h"
+#include "vgui_controls/ScrollBarSlider.h"
+#include <vgui_controls/Label.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui_controls/Tooltip.h>
+#include <vgui_controls/AnimationController.h>
+#include "clientmode_tf.h"
+
+using namespace vgui;
+
+#ifdef STAGING_ONLY
+extern ConVar tf_use_card_tooltips;
+#endif // STAGING_ONLY
+
+//-----------------------------------------------------------------------------
+// Purpose: A label that can have multiple fonts specified and will try to use
+// them in order specified, using the first one that fits.
+//-----------------------------------------------------------------------------
+class CAutoFittingLabel : public Label
+{
+ DECLARE_CLASS_SIMPLE( CAutoFittingLabel, Label );
+public:
+
+ CAutoFittingLabel( Panel *parent, const char *name )
+ : Label( parent, name, (const char*)NULL )
+ {}
+
+ virtual void ApplySettings( KeyValues *inResourceData )
+ {
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pFonts = inResourceData->FindKey( "fonts" );
+ if ( pFonts )
+ {
+ vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+
+ // Get all the fonts
+ FOR_EACH_SUBKEY( pFonts, pFont )
+ {
+ const HFont& font = pScheme->GetFont( pFont->GetString( "font" ), true );
+ m_vecFonts.AddToTail( font );
+ }
+ }
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ SetFont( m_vecFonts.Head() );
+
+ // Go through all the fonts and try to find one that fits
+ int nIndex = 0;
+ GetTextImage()->ResizeImageToContentMaxWidth( GetWide() );
+ while ( ( GetTextImage()->IsWrapping() || GetTextImage()->GetEllipsesPosition() ) && nIndex < m_vecFonts.Count() )
+ {
+ SetFont( m_vecFonts[ nIndex ] );
+ GetTextImage()->ResizeImageToContentMaxWidth( GetWide() );
+
+ ++nIndex;
+ }
+ }
+
+private:
+
+ CUtlVector< HFont > m_vecFonts;
+};
+
+DECLARE_BUILD_FACTORY( CAutoFittingLabel );
+
+
+DECLARE_BUILD_FACTORY( CRepeatingContainer );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CRepeatingContainer::CRepeatingContainer( Panel *pParent, const char *pszName )
+ : EditablePanel( pParent, pszName )
+ , m_eLayoutMethod( METHOD_EVEN )
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CRepeatingContainer::~CRepeatingContainer()
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRepeatingContainer::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues* pCommonSettings = inResourceData->FindKey( "CommonSettings" );
+ KeyValues* pIndividualSettings = inResourceData->FindKey( "IndividualSettings" );
+
+ // Delete old panels
+ m_vecChildren.PurgeAndDeleteElements();
+
+ if ( pIndividualSettings && pCommonSettings )
+ {
+ // Go through every individual panel
+ FOR_EACH_SUBKEY( pIndividualSettings, pSubKey )
+ {
+ // Merge the individual keys onto the common keys, keeping "individual" values if there's a conflict
+ pSubKey->RecursiveMergeKeyValues( pCommonSettings );
+
+ // Create each panel
+ Panel *pNewPanel = CreateControlByName( pSubKey->GetString( "ControlName" ) );
+ if ( pNewPanel )
+ {
+ pNewPanel->SetParent( this );
+ pNewPanel->SetBuildGroup( GetBuildGroup() );
+ pNewPanel->ApplySettings( pSubKey );
+ m_vecChildren.AddToTail( pNewPanel );
+ }
+ }
+ }
+
+ const char *pszSpacingMethod = inResourceData->GetString( "spacing_method", NULL );
+ if ( pszSpacingMethod )
+ {
+ // Figure out how we're going to layout all these panels
+ if ( FStrEq( pszSpacingMethod, "METHOD_STEP" ) )
+ {
+ m_eLayoutMethod = METHOD_STEP;
+ }
+ else // Default to event
+ {
+ m_eLayoutMethod = METHOD_EVEN;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRepeatingContainer::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ // No children? We're done.
+ if ( m_vecChildren.IsEmpty() )
+ return;
+
+ // Fixed step gaps
+ if ( m_eLayoutMethod == METHOD_STEP )
+ {
+ FOR_EACH_VEC( m_vecChildren, i )
+ {
+ m_vecChildren[i]->SetPos( m_iXStep * i , 0 );
+ }
+ }
+ else // default METHOD_EVEN
+ {
+ // Evently spaced
+ int nParentWide = GetWide();
+ int nTotalChildWide = 0;
+
+ FOR_EACH_VEC( m_vecChildren, i )
+ {
+ nTotalChildWide += m_vecChildren[i]->GetWide();
+ }
+
+ int nXStep = 0;
+ if ( nTotalChildWide < nParentWide )
+ {
+ nXStep = ( nParentWide - nTotalChildWide ) / ( m_vecChildren.Count() - 1 );
+ }
+
+ int nXPos = 0;
+ FOR_EACH_VEC( m_vecChildren, i )
+ {
+ m_vecChildren[i]->SetPos( nXPos, 0 );
+ nXPos += m_vecChildren[i]->GetWide() + nXStep;
+ }
+ }
+}
+
+
+DECLARE_BUILD_FACTORY( CTFItemCardPanel );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItemCardPanel::CTFItemCardPanel( Panel *pParent, const char *pszName )
+ : BaseClass( pParent, pszName )
+ , m_pItem( NULL )
+ , m_bAllControlsValid( false )
+ , m_bPinned( false )
+ , m_pDropShadow( NULL )
+ , m_pRarityBackgroundOverlay( NULL )
+ , m_pCardTop( NULL )
+ , m_pItemModel( NULL )
+ , m_pRarityContainer( NULL )
+ , m_pItemName( NULL )
+ , m_pRarityName( NULL )
+ , m_pInfoContainer( NULL )
+ , m_pClassLabel( NULL )
+ , m_pClassIconContainer( NULL )
+ , m_pTypeLabel( NULL )
+ , m_pTypeLabelValue( NULL )
+ , m_pExteriorLabel( NULL )
+ , m_pExteriorLabelValue( NULL )
+ , m_pBottomContainer( NULL )
+ , m_pBottomScrollingContainer( NULL )
+ , m_pAttribsLabel( NULL )
+ , m_pEquipSlotLabel( NULL )
+{
+ m_pDropShadow = new ImagePanel( pParent, "ItemCardShadow" );
+ m_pDropShadow->SetVisible( false );
+ m_pDropShadow->SetAutoDelete( false ); // We'll delete this panel
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItemCardPanel::~CTFItemCardPanel()
+{
+ m_pDropShadow->MarkForDeletion();
+ m_pDropShadow = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Do FindControl() but also verify that we got what we were looking for
+//-----------------------------------------------------------------------------
+template < class T >
+T* CTFItemCardPanel::FindAndVerifyControl( Panel* pParent, const char* pszPanelName )
+{
+ if ( !m_bAllControlsValid )
+ return NULL;
+
+ // Find the panel
+ T* pChild = pParent->FindControl< T >( pszPanelName, true );
+ // Make sure it's still there
+ m_bAllControlsValid &= pChild != NULL;
+
+ return pChild;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadResFileForCurrentItem();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( !m_pItem )
+ {
+ return;
+ }
+
+ if ( !m_bAllControlsValid )
+ {
+ return;
+ }
+
+ m_pBottomScrollingContainer->InvalidateLayout();
+
+ UpdateDescription();
+ UpdateModelOrIcon();
+
+ // Position grime
+ {
+ // Randomize based on our original item ID, if we have one. If not, just use defindex
+ RandomSeed( m_pItem->GetSOCData() ? m_pItem->GetSOCData()->GetOriginalID() : m_pItem->GetItemDefIndex() );
+
+ // Randomize X/Y
+ int nGrimeX = RandomInt( -abs( m_pGrime->GetWide() - GetWide() ), 0 );
+ int nGrimeY = RandomInt( -abs( m_pGrime->GetTall() - GetTall() ), 0 );
+ m_pGrime->SetPos( nGrimeX, nGrimeY );
+ // Randomize 0,90,180,270 rotation
+ m_pGrime->GetImage()->SetRotation( RandomInt( 0, 3 ) ); // Have to GetImage()->SetRotation because ImagePanel::SetRotation does nothing!
+ }
+
+ // Update our shadow's settings
+ {
+ m_pDropShadow->SetZPos( GetZPos() - 1 );
+ m_pDropShadow->SetShouldScaleImage( true );
+ m_pDropShadow->SetMouseInputEnabled( false );
+ m_pDropShadow->SetImage( "item_card/standard_background_dropshadow" );
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::SetVisible( bool bVisible )
+{
+ // Update the position of our external shadow panel
+ if ( m_bAllControlsValid )
+ {
+ int x=0,y=0,wide,tall,xTemp,yTemp;
+ m_pBackground->GetBounds( xTemp, yTemp, wide, tall );
+ x += xTemp; y += yTemp;
+ m_pMainContainer->GetPos( xTemp, yTemp );
+ x += xTemp; y += yTemp;
+ GetPos( xTemp, yTemp );
+ x += xTemp; y += yTemp;
+
+ m_pDropShadow->SetBounds( x + m_iShadowOffset, y + m_iShadowOffset, wide * 1.15f, tall * 1.15f );
+ }
+
+ BaseClass::SetVisible( bVisible );
+
+ m_pDropShadow->SetVisible( bVisible );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Force the scrolling container to have mouse input matching the panel's
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::SetMouseInputEnabled( bool state )
+{
+ BaseClass::SetVisible( state );
+
+ if ( m_bAllControlsValid )
+ {
+ m_pBottomScrollingContainer->SetMouseInputEnabled( state );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::SetItem( CEconItemView* pItem )
+{
+ m_pItem = pItem;
+
+ // Update the panels
+ LoadResFileForCurrentItem();
+ MakeReadyForUse();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::PinCard( bool bPin )
+{
+#ifdef STAGING_ONLY
+ if ( !tf_use_card_tooltips.GetBool() )
+ {
+ return;
+ }
+#endif // STAGING_ONLY
+
+ bool bDiff = bPin != m_bPinned;
+ m_bPinned = bPin;
+
+ if ( bDiff && bPin )
+ {
+ g_pClientMode->GetViewportAnimationController()->CancelAnimationsForPanel( this );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_HidePinHint" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_ShowCloseButton" );
+ SetVisible( true );
+ }
+ else if ( bDiff && !bPin )
+ {
+ g_pClientMode->GetViewportAnimationController()->CancelAnimationsForPanel( this );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_ShowPinHint" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "ItemCard_HideCloseButton" );
+ SetVisible( false );
+ }
+
+ // Force mouse input
+ SetMouseInputEnabled( bPin );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::UpdateDescription()
+{
+ const GameItemDefinition_t *pItemDef = m_pItem->GetItemDefinition();
+ if ( !pItemDef )
+ return;
+
+ // Itm name
+ m_pItemName->SetText( m_pItem->GetItemName() );
+
+ // If we dont have a rarity, then we assume we're an "old" item.
+ if ( !GetItemSchema()->GetRarityColor(pItemDef->GetRarity() ) )
+ {
+ // Grab the item quality (ie. strange, unusual)
+ const char *pszQualityColorString = EconQuality_GetColorString( (EEconItemQuality)m_pItem->GetItemQuality() );
+ if ( m_pItem->IsValid() && pszQualityColorString )
+ {
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ Color colorName = pScheme->GetColor( pszQualityColorString, Color( 0, 255, 0, 255 ) );
+ m_pItemName->SetFgColor( colorName );
+ }
+ }
+
+
+ // Set highlighting on the class icons
+ {
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ CExImageButton *pExImage = dynamic_cast< CExImageButton* >( m_pClassIconContainer->GetRepeatingChild( GetRemappedMenuIndexForClass(i) - 1 ) );
+ if ( pExImage )
+ {
+ pExImage->SetSelected( pItemDef->CanBeUsedByClass( i ) );
+ }
+ }
+ }
+
+ // Set type name into the label
+ {
+ const locchar_t *locTypename = g_pVGuiLocalize->Find( pItemDef->GetItemTypeName() );
+ m_pTypeLabelValue->SetText( locTypename );
+ }
+
+ const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItemDef->GetRarity() );
+
+ // Setup the rarity color overlay
+ {
+ attrib_colors_t attribColor = ATTRIB_COL_RARITY_DEFAULT;
+
+ if ( pItemRarity )
+ {
+ attribColor = pItemRarity->GetAttribColor();
+ }
+
+ vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ Color color = pScheme->GetColor( GetColorNameForAttribColor( attribColor ), Color( 255, 255, 255, 255 ) );
+ m_pRarityBackgroundOverlay->SetDrawColor( color );
+ m_pRarityName->SetFgColor( color );
+ }
+
+ // Rarity name into the label
+ {
+ const char *pszRarityName = "#Rarity_Default";
+ if ( pItemRarity )
+ {
+ pszRarityName = pItemRarity->GetLocKey();
+ }
+
+ m_pRarityName->SetText( g_pVGuiLocalize->Find( pszRarityName ) );
+ }
+
+ enum { kAttribBufferSize = 4 * 1024 };
+ wchar_t wszAttribBuffer[ kAttribBufferSize ] = L"";
+
+ // Space out the attributes
+ const CEconItemDescription *pDescription = m_pItem->GetDescription();
+ if ( pDescription )
+ {
+ unsigned int unWrittenLines = 0;
+ for ( unsigned int i = 0; i < pDescription->GetLineCount(); i++ )
+ {
+ const econ_item_description_line_t& line = pDescription->GetLine(i);
+ if ( (line.unMetaType & ( kDescLineFlag_Name ) ) == 0 )
+ {
+ V_wcscat_safe( wszAttribBuffer, L"\n" ); // add empty lines everywhere
+ V_wcscat_safe( wszAttribBuffer, line.sText.Get() );
+ ++unWrittenLines;
+ }
+ }
+
+ // Get all the attributes
+ Assert( m_pItem->GetDescription() );
+ if ( m_pAttribsLabel->GetTextImage() && m_pItem->GetDescription() )
+ {
+ m_pAttribsLabel->SetText( wszAttribBuffer );
+
+ TextImage *pTextImage = m_pAttribsLabel->GetTextImage();
+ Assert( pTextImage );
+
+ pTextImage->ClearColorChangeStream();
+
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+
+ Color prevCol;
+ unsigned int unCurrentTextStreamIndex = 0;
+ for ( unsigned int i = 0; i < pDescription->GetLineCount(); i++ )
+ {
+ const econ_item_description_line_t& line = pDescription->GetLine(i);
+
+ // Ignore the name line, it was added above
+ if ( ( line.unMetaType & ( kDescLineFlag_Name ) ) != 0 )
+ {
+ continue;
+ }
+
+ Color col = pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) );
+
+ // Output a color change if necessary.
+ if ( i == 0 || prevCol != col )
+ {
+ pTextImage->AddColorChange( col, unCurrentTextStreamIndex );
+ prevCol = col;
+ }
+
+ unCurrentTextStreamIndex += StringFuncs<locchar_t>::Length( line.sText.Get() ) + 1; // add one character to deal with newlines
+ }
+
+ int nWide, nTall;
+ pTextImage->GetContentSize( nWide, nTall );
+ m_pAttribsLabel->SetTall( nTall );
+ }
+ }
+
+ // Set equip slot
+ {
+ int nEquipSlot = pItemDef->GetDefaultLoadoutSlot();
+ if ( nEquipSlot != -1 )
+ {
+ m_pEquipSlotLabel->SetText( g_pVGuiLocalize->Find( GetItemSchema()->GetLoadoutStringsForDisplay( pItemDef->GetEquipType() )[ nEquipSlot ] ) );
+ }
+ else
+ {
+ m_pEquipSlotLabel->SetText( "" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::UpdateModelOrIcon()
+{
+ if ( !m_pItem )
+ {
+ return;
+ }
+
+ m_pItemModel->SetItem( m_pItem );
+
+ const char *pszModelName = m_pItem->GetPlayerDisplayModel( 0, 0 );
+ if ( pszModelName )
+ {
+ MDLHandle_t hMDL = mdlcache->FindMDL( pszModelName );
+ m_pItemModel->SetMDL( hMDL, static_cast<IClientRenderable*>(m_pItem) );
+ mdlcache->Release( hMDL ); // counterbalance addref from within FindMDL
+ m_pItemModel->SetForceModelUsage( true );
+ }
+ else
+ {
+ m_pItemModel->SetInventoryImageType( CEmbeddedItemModelPanel::IMAGETYPE_LARGE );
+ m_pItemModel->LoadInventoryImage();
+ m_pItemModel->SetForceModelUsage( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemCardPanel::LoadResFileForCurrentItem()
+{
+ // Temp hack. New items get the new cards
+ const char *pszResFile = "Resource/UI/econ/ItemCardPanel_Series1.res";
+ if ( m_pItem )
+ {
+ const GameItemDefinition_t *pItemDef = m_pItem->GetItemDefinition();
+ if ( pItemDef )
+ {
+ const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItemDef->GetRarity() );
+ if ( pItemRarity && pItemRarity->GetDBValue() > 0 )
+ {
+ pszResFile = "Resource/UI/econ/ItemCardPanel_Series2.res";
+ }
+ }
+ }
+
+ m_bAllControlsValid = false;
+
+ LoadControlSettings( pszResFile );
+
+ m_bAllControlsValid = true;
+ // Grab all the controls...
+ m_pMainContainer = FindAndVerifyControl< EditablePanel >( this, "MainContainer" );
+ m_pRarityBackgroundOverlay = FindAndVerifyControl< ImagePanel >( m_pMainContainer, "RarityBackgroundOverlay" );
+ m_pBackground = FindAndVerifyControl< ImagePanel >( m_pMainContainer, "Background" );
+ m_pGrime = FindAndVerifyControl< ImagePanel >( m_pMainContainer, "GrimeLayer" );
+ m_pCardTop = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "CardTop" );
+ m_pItemModel = FindAndVerifyControl< CEmbeddedItemModelPanel >( m_pCardTop, "ItemModel" );
+ m_pRarityContainer = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "RarityContainer" );
+ m_pItemName = FindAndVerifyControl< Label >( m_pRarityContainer, "ItemNameLabel" );
+ m_pRarityName = FindAndVerifyControl< Label >( m_pRarityContainer, "ItemRarityLabel" );
+ m_pClassIconContainer = FindAndVerifyControl< CRepeatingContainer >( m_pMainContainer, "ClassIconContainer" );
+ m_pInfoContainer = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "InfoContainer" );
+ m_pClassLabel = FindAndVerifyControl< Label >( m_pInfoContainer, "ClassLabel" );
+ m_pTypeLabel = FindAndVerifyControl< Label >( m_pInfoContainer, "TypeLabel" );
+ m_pTypeLabelValue = FindAndVerifyControl< Label >( m_pInfoContainer, "TypeValueLabel" );
+ m_pExteriorLabel = FindAndVerifyControl< Label >( m_pInfoContainer, "ExteriorLabel" );
+ m_pExteriorLabelValue = FindAndVerifyControl< Label >( m_pInfoContainer, "ExteriorValueLabel" );
+ m_pBottomContainer = FindAndVerifyControl< EditablePanel >( m_pMainContainer, "BottomContainer" );
+ m_pBottomScrollingContainer = FindAndVerifyControl< CExScrollingEditablePanel >( m_pBottomContainer, "ScrollableBottomContainer" );
+ m_pAttribsLabel = FindAndVerifyControl< Label >( m_pBottomScrollingContainer, "AttribsLabel" );
+ m_pEquipSlotLabel = FindAndVerifyControl< Label >( this, "EquipSlotLabel" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemCardPanelToolTip::CItemCardPanelToolTip( Panel *parent, const char *text )
+: BaseTooltip( parent, text )
+, m_pMouseOverItemPanel( NULL )
+, m_iPositioningStrategy( IPTTP_BOTTOM_SIDE )
+{
+ m_hCurrentPanel = NULL;
+ SetTooltipDelay( 100 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemCardPanelToolTip::GetPosition( itempanel_tooltippos_t iTooltipPosition, CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos )
+{
+ switch ( iTooltipPosition )
+ {
+ case IPTTP_LEFT:
+ *iXPos = ( iItemX - m_pMouseOverItemPanel->GetWide() );
+ *iYPos = ( iItemY + pItemPanel->GetTall() * 0.5f ) - ( m_pMouseOverItemPanel->GetTall() * 0.5f );
+ break;
+ case IPTTP_RIGHT:
+ *iXPos = ( iItemX + pItemPanel->GetWide() );
+ *iYPos = ( iItemY + pItemPanel->GetTall() * 0.5f ) - ( m_pMouseOverItemPanel->GetTall() * 0.5f );
+ break;
+ }
+
+ *iYPos = Clamp( *iYPos, (int)YRES( -30 ), int( m_pParentPanel->GetTall() - m_pMouseOverItemPanel->GetTall() - YRES( 30 ) ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemCardPanelToolTip::ValidatePosition( CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos )
+{
+ if ( *iXPos < 0 )
+ return false;
+
+ if ( ( *iXPos + m_pMouseOverItemPanel->GetWide() ) > m_pParentPanel->GetWide() )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemCardPanelToolTip::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( !ShouldLayout() )
+ return;
+
+ _isDirty = false;
+
+ CItemModelPanel *pItemPanel = m_hCurrentPanel.Get();
+ if ( m_pMouseOverItemPanel && pItemPanel && !m_pMouseOverItemPanel->IsPinned() )
+ {
+ CEconItemView *pItem = pItemPanel->GetItem();
+ if ( pItem )
+ {
+ m_pMouseOverItemPanel->SetItem( pItem );
+
+
+ int x,y;
+
+ // If the panel is somewhere in a derived class, we need to get its position in our space
+ if ( pItemPanel->GetParent() != m_pMouseOverItemPanel->GetParent() )
+ {
+ int iItemAbsX, iItemAbsY;
+ ipanel()->GetAbsPos( pItemPanel->GetVPanel(), iItemAbsX, iItemAbsY );
+ int iParentAbsX, iParentAbsY;
+ ipanel()->GetAbsPos( m_pMouseOverItemPanel->GetParent()->GetVPanel(), iParentAbsX, iParentAbsY );
+
+ x = (iItemAbsX - iParentAbsX);
+ y = (iItemAbsY - iParentAbsY);
+ }
+ else
+ {
+ pItemPanel->GetPos( x, y );
+ }
+
+ int iXPos = 0;
+ int iYPos = 0;
+
+ // Loop through the positions in our strategy, and hope we find a valid spot
+ for ( int i = 0; i < NUM_POSITIONS_PER_STRATEGY; i++ )
+ {
+ itempanel_tooltippos_t iPos = g_iTooltipStrategies[m_iPositioningStrategy][i];
+ if ( iPos != IPTTP_LEFT && iPos != IPTTP_RIGHT )
+ continue;
+
+ GetPosition( iPos, pItemPanel, x, y, &iXPos, &iYPos );
+
+ if ( ValidatePosition( pItemPanel, x, y, &iXPos, &iYPos ) )
+ break;
+ }
+
+ m_pMouseOverItemPanel->SetPos( iXPos, iYPos );
+ m_pMouseOverItemPanel->SetVisible( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemCardPanelToolTip::ShowTooltip( Panel *currentPanel )
+{
+ if ( m_pMouseOverItemPanel && currentPanel != m_hCurrentPanel.Get() )
+ {
+ CItemModelPanel *pItemPanel = assert_cast<CItemModelPanel *>(currentPanel);
+ m_hCurrentPanel.Set( pItemPanel );
+ pItemPanel->PostActionSignal( new KeyValues("ItemPanelEntered") );
+ vgui::surface()->PlaySound( "ui/item_info_mouseover.wav" );
+ }
+ BaseClass::ShowTooltip( currentPanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemCardPanelToolTip::HideTooltip()
+{
+ if ( m_pMouseOverItemPanel )
+ {
+ if ( m_pMouseOverItemPanel->IsPinned() )
+ return;
+
+ m_pMouseOverItemPanel->SetVisible( false );
+ }
+
+ if ( m_hCurrentPanel )
+ {
+ m_hCurrentPanel.Get()->PostActionSignal( new KeyValues("ItemPanelExited") );
+ m_hCurrentPanel = NULL;
+ }
+}
diff --git a/game/client/tf/vgui/tf_item_card_panel.h b/game/client/tf/vgui/tf_item_card_panel.h
new file mode 100644
index 0000000..602991e
--- /dev/null
+++ b/game/client/tf/vgui/tf_item_card_panel.h
@@ -0,0 +1,159 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_ITEM_CARD_PANEL_H
+#define TF_ITEM_CARD_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/EditablePanel.h>
+#include "item_model_panel.h"
+#include "tf_controls.h"
+
+class CEconItemView;
+class CEmbeddedItemModelPanel;
+namespace vgui
+{
+ class ScrollBar;
+ class ImagePanel;
+}
+class CIconPanel;
+
+using namespace vgui;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: A simple container that contains repeating elements with common
+// and individual characteristics
+//-----------------------------------------------------------------------------
+class CRepeatingContainer : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CRepeatingContainer, EditablePanel );
+public:
+ CRepeatingContainer( Panel *pParent, const char *pszName );
+ virtual ~CRepeatingContainer();
+
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+
+ Panel* GetRepeatingChild( int nIndex ) const { return m_vecChildren[ nIndex ]; }
+private:
+
+ enum ELayoutMethod_t
+ {
+ METHOD_EVEN,
+ METHOD_STEP,
+ };
+
+ CUtlVector< Panel* > m_vecChildren;
+ ELayoutMethod_t m_eLayoutMethod;
+ CPanelAnimationVarAliasType( int, m_iXStep, "x_step", "0", "proportional_xpos" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A representation of an econ item as a collectible card
+//-----------------------------------------------------------------------------
+class CTFItemCardPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFItemCardPanel, EditablePanel );
+public:
+ CTFItemCardPanel( Panel *parent, const char *name );
+ virtual ~CTFItemCardPanel( void );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void SetVisible( bool bVisible ) OVERRIDE;
+ virtual void SetMouseInputEnabled( bool state ) OVERRIDE;
+
+ void SetItem( CEconItemView* pItem );
+ CEconItemView* GetItem() { return m_pItem; }
+
+ void PinCard( bool bPin );
+ bool IsPinned() const { return m_bPinned; }
+private:
+
+ void UpdateDescription();
+ void UpdateModelOrIcon();
+ void LoadResFileForCurrentItem();
+
+ template < class T >
+ T* FindAndVerifyControl( Panel* pParent, const char* pszPanelName );
+
+
+ CEconItemView* m_pItem;
+
+ ImagePanel *m_pDropShadow;
+ CExImageButton *m_pCloseButton;
+
+ ImagePanel *m_pBackground;
+ ImagePanel *m_pGrime;
+ ImagePanel *m_pRarityBackgroundOverlay;
+ EditablePanel *m_pMainContainer;
+
+ EditablePanel *m_pCardTop;
+ CEmbeddedItemModelPanel *m_pItemModel;
+
+ EditablePanel *m_pRarityContainer;
+ Label *m_pItemName;
+ Label *m_pRarityName;
+
+ EditablePanel *m_pInfoContainer;
+ Label *m_pClassLabel;
+ CRepeatingContainer *m_pClassIconContainer;
+ Label *m_pTypeLabel;
+ Label *m_pTypeLabelValue;
+ Label *m_pExteriorLabel;
+ Label *m_pExteriorLabelValue;
+
+ EditablePanel *m_pBottomContainer;
+ CExScrollingEditablePanel *m_pBottomScrollingContainer;
+ Label *m_pAttribsLabel;
+ Label *m_pEquipSlotLabel;
+
+ bool m_bAllControlsValid;
+ bool m_bPinned;
+
+ CPanelAnimationVarAliasType( int, m_iShadowOffset, "shadowoffset", "5", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Item model panel tooltip. Calls setvisible on the controlled panel
+// and positions it below/above the current panel.
+//-----------------------------------------------------------------------------
+class CItemCardPanelToolTip : public vgui::BaseTooltip
+{
+ DECLARE_CLASS_SIMPLE( CItemCardPanelToolTip, vgui::BaseTooltip );
+public:
+ CItemCardPanelToolTip(vgui::Panel *parent, const char *text = NULL);
+
+ void SetText(const char *text) { return; }
+ const char *GetText() { return NULL; }
+
+ virtual void PerformLayout();
+ virtual void ShowTooltip( vgui::Panel *currentPanel );
+ virtual void HideTooltip();
+
+ void SetupPanels( vgui::Panel *pParentPanel, CTFItemCardPanel *pMouseOverItemPanel ) { m_pParentPanel = pParentPanel; m_pMouseOverItemPanel = pMouseOverItemPanel; }
+ void SetPositioningStrategy( itempanel_tooltip_strategies_t iStrat ) { m_iPositioningStrategy = iStrat; }
+
+private:
+ void GetPosition( itempanel_tooltippos_t iTooltipPosition, CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos );
+ bool ValidatePosition( CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos );
+
+private:
+ CTFItemCardPanel *m_pMouseOverItemPanel; // This is the tooltip panel we make visible. Must be a CItemModelPanel.
+ vgui::Panel *m_pParentPanel; // This is the panel that we send item entered/exited messages to
+ vgui::DHANDLE<CItemModelPanel> m_hCurrentPanel;
+
+ itempanel_tooltip_strategies_t m_iPositioningStrategy;
+ bool m_bHorizontalPreferLeft;
+};
+
+#endif // TF_ITEM_CARD_PANEL_H
diff --git a/game/client/tf/vgui/tf_item_inspection_panel.cpp b/game/client/tf/vgui/tf_item_inspection_panel.cpp
new file mode 100644
index 0000000..8b3fc57
--- /dev/null
+++ b/game/client/tf/vgui/tf_item_inspection_panel.cpp
@@ -0,0 +1,391 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_item_inspection_panel.h"
+#include "item_model_panel.h"
+#include "navigationpanel.h"
+#include "gc_clientsystem.h"
+#include "econ_ui.h"
+#include "backpack_panel.h"
+#include "vgui_int.h"
+#include "cdll_client_int.h"
+#include "clientmode_tf.h"
+#include "ienginevgui.h"
+#include "tf_hud_mainmenuoverride.h"
+#include "econ_item_system.h"
+
+using namespace vgui;
+
+#ifdef STAGING_ONLY
+void cc_toggle_debug_inspect( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ // Too early?
+ if ( ItemSystem()->GetItemSchema()->GetVersion() == 0 )
+ return;
+
+ ConVarRef var( pConVar );
+ if ( var.IsValid() )
+ {
+ CTFItemInspectionPanel* pInspectionPanel = dynamic_cast< CTFItemInspectionPanel* >( EconUI()->GetBackpackPanel()->FindChildByName( "InspectionPanel" ) );
+ if ( pInspectionPanel )
+ {
+ bool bVisible = pInspectionPanel->IsVisible(); // Save visibility
+ pInspectionPanel->Reset();
+ pInspectionPanel->SetVisible( bVisible ); // Restore visibility
+ }
+ }
+}
+
+
+
+ConVar tf_use_debug_inspection_panel( "tf_use_debug_inspection_panel", "0", FCVAR_ARCHIVE, "Toggles developer controls for the item inspection panel", cc_toggle_debug_inspect );
+extern ConVar tf_paint_kit_force_regen;
+#endif
+
+
+
+// ********************************************************************************************************************************
+// Given proto data, create an inspect panel
+static void Helper_LaunchPreviewWithPreviewDataBlock( CEconItemPreviewDataBlock const &protoData )
+{
+ // Create an econ item view
+ CEconItem econItem;
+ econItem.DeserializeFromProtoBufItem( protoData.econitem() );
+
+ CEconItemView itemView;
+ itemView.Init( econItem.GetDefinitionIndex(), econItem.GetQuality(), econItem.GetItemLevel(), econItem.GetAccountID() );
+ itemView.SetNonSOEconItem( &econItem );
+
+ // Get the Backpack to tell the inspect panel to render
+ if ( EconUI()->GetBackpackPanel() )
+ {
+ EconUI()->GetBackpackPanel()->OpenInspectModelPanelAndCopyItem( &itemView );
+ }
+}
+
+// ********************************************************************************************************************************
+// Request an item from the GC to inspect
+static void Helper_RequestEconActionPreview( uint64 paramS, uint64 paramA, uint64 paramD, uint64 paramM )
+{
+ if ( !paramA || !paramD )
+ return;
+ if ( !paramS && !paramM )
+ return;
+
+ static double s_flTime = 0.0f;
+ double flNow = Plat_FloatTime();
+ if ( s_flTime && ( flNow - s_flTime <= 2.5 ) )
+ return;
+
+ s_flTime = flNow;
+ GCSDK::CProtoBufMsg< CMsgGC_Client2GCEconPreviewDataBlockRequest > msg( k_EMsgGC_Client2GCEconPreviewDataBlockRequest );
+ msg.Body().set_param_s( paramS );
+ msg.Body().set_param_a( paramA );
+ msg.Body().set_param_d( paramD );
+ msg.Body().set_param_m( paramM );
+ GCClientSystem()->GetGCClient()->BSendMessage( msg );
+}
+
+// ********************************************************************************************************************************
+class CGCClient2GCEconPreviewDataBlockResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCClient2GCEconPreviewDataBlockResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
+
+ virtual bool BYieldingRunJobFromMsg( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgGC_Client2GCEconPreviewDataBlockResponse> msg( pNetPacket );
+ Helper_LaunchPreviewWithPreviewDataBlock( msg.Body().iteminfo() );
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCClient2GCEconPreviewDataBlockResponse, "CGCClient2GCEconPreviewDataBlockResponse", k_EMsgGC_Client2GCEconPreviewDataBlockResponse, GCSDK::k_EServerTypeGCClient );
+
+// ********************************************************************************************************************************
+CON_COMMAND_F( tf_econ_item_preview, "Preview an economy item", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_CLIENTCMD_CAN_EXECUTE )
+{
+ if ( args.ArgC() < 2 )
+ return;
+
+ //// kill the workshop preview dialog if it's up
+ //if ( g_pWorkshopWorkbenchDialog )
+ //{
+ // delete g_pWorkshopWorkbenchDialog;
+ // g_pWorkshopWorkbenchDialog = NULL;
+ //}
+
+ //extern float g_flReadyToCheckForPCBootInvite;
+ //if ( !g_flReadyToCheckForPCBootInvite || !gpGlobals->curtime || !gpGlobals->framecount )
+ //{
+ // ConMsg( "Deferring csgo_econ_action_preview command!\n" );
+ // return;
+ //}
+
+ // Encoded parameter, validate basic length
+ char const *pchEncodedAscii = args.Arg( 1 );
+ int nLen = Q_strlen( pchEncodedAscii );
+ if ( nLen <= 16 ) { Assert( 0 ); return; }
+
+ // If we are launched with new format requesting steam_ownerid and assetid then do async query
+ if ( *pchEncodedAscii == 'S' )
+ {
+ uint64 uiParamS = Q_atoui64( pchEncodedAscii + 1 );
+ uint64 uiParamA = 0;
+ uint64 uiParamD = 0;
+ if ( char const *pchParamA = strchr( pchEncodedAscii, 'A' ) )
+ {
+ uiParamA = Q_atoui64( pchParamA + 1 );
+ if ( char const *pchParamD = strchr( pchEncodedAscii, 'D' ) )
+ {
+ uiParamD = Q_atoui64( pchParamD + 1 );
+ Helper_RequestEconActionPreview( uiParamS, uiParamA, uiParamD, 0ull );
+ }
+ }
+ return;
+ }
+
+ // Else if we are launched with new format requesting market listing id and assetid then do async query
+ if ( *pchEncodedAscii == 'M' )
+ {
+ uint64 uiParamM = Q_atoui64( pchEncodedAscii + 1 );
+ uint64 uiParamA = 0;
+ uint64 uiParamD = 0;
+ if ( char const *pchParamA = strchr( pchEncodedAscii, 'A' ) )
+ {
+ uiParamA = Q_atoui64( pchParamA + 1 );
+ if ( char const *pchParamD = strchr( pchEncodedAscii, 'D' ) )
+ {
+ uiParamD = Q_atoui64( pchParamD + 1 );
+ Helper_RequestEconActionPreview( 0ull, uiParamA, uiParamD, uiParamM );
+ }
+ }
+ return;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItemInspectionPanel::CTFItemInspectionPanel( Panel* pPanel, const char *pszName )
+ : BaseClass( pPanel, pszName )
+ , m_pModelInspectPanel( NULL )
+ , m_pItemViewData( NULL )
+ , m_pSOEconItemData( NULL )
+{
+ m_pModelInspectPanel = new CEmbeddedItemModelPanel( this, "ModelInspectionPanel" );
+ m_pTeamColorNavPanel = new CNavigationPanel( this, "TeamNavPanel" );
+ m_pItemNamePanel = new CItemModelPanel( this, "ItemName" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ const char* pszFileName = "Resource/UI/econ/InspectionPanel.res";
+#ifdef STAGING_ONLY
+ if ( tf_use_debug_inspection_panel.GetBool() )
+ {
+ pszFileName = "Resource/UI/econ/InspectionPanel_Dev.res";
+ }
+#endif
+
+ LoadControlSettings( pszFileName );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::PerformLayout()
+{
+ CEconItemView* pItem = m_pModelInspectPanel->GetItem();
+ if ( pItem && pItem->IsValid() )
+ {
+ // Find out if we should show the team buttons if the item has team skins
+ if ( m_pTeamColorNavPanel )
+ {
+ static CSchemaAttributeDefHandle pAttrTeamColoredPaintkit( "has team color paintkit" );
+ uint32 iHasTeamColoredPaintkit = 0;
+
+ // Find out if the item has the attribute
+ bool bShowTeamButton = FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrTeamColoredPaintkit, &iHasTeamColoredPaintkit ) && iHasTeamColoredPaintkit > 0;
+
+ m_pTeamColorNavPanel->SetVisible( bShowTeamButton );
+ }
+
+ // Show only the name if there's no rarity
+ //m_pItemNamePanel->SetNameOnly( true );
+
+ // Force the description to update right now, or else it might be caught up
+ // in the queue of 50 panels in the backpack which want to load their crap first
+ m_pItemNamePanel->UpdateDescription();
+ }
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "close" ) )
+ {
+ // Clean up after ourselves and set the item back to red
+ CEconItemView* pItem = m_pModelInspectPanel->GetItem();
+ if ( pItem )
+ {
+ pItem->SetTeamNumber( TF_TEAM_RED );
+ // We need to recomposite again because we might have a blue version
+ // in the cache that might get used in a tooltip.
+ RecompositeItem();
+ }
+
+ SetVisible( false );
+ return;
+ }
+#ifdef STAGING_ONLY
+ else if ( FStrEq( command, "newseed" ) )
+ {
+ ConVarRef cv_seed( "tf_paint_kit_seed_override" );
+ cv_seed.SetValue( RandomInt( 0, 10000000 ) );
+ RecompositeItem();
+ return;
+ }
+ else if ( FStrEq( command, "changeteam" ) )
+ {
+ ConVarRef cv_team( "tf_paint_kit_team_override" );
+ cv_team.SetValue ( cv_team.GetInt() == 0 ? 1 : 0 );
+ RecompositeItem();
+ return;
+ }
+ else if ( V_strncmp( "wear", command, 4 ) == 0 )
+ {
+ int nWearLevel = atoi( command + 4 );
+ ConVarRef cv_wear( "tf_paint_kit_force_wear" );
+ cv_wear.SetValue( nWearLevel );
+ RecompositeItem();
+ return;
+ }
+ else if ( FStrEq( command, "refresh_skin" ) )
+ {
+ engine->ClientCmd_Unrestricted( "tf_paint_kit_override_refresh" );
+ RecompositeItem();
+ return;
+ }
+#endif
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::SetItem( CEconItemView *pItem )
+{
+ m_pItemNamePanel->SetItem( pItem );
+ m_pModelInspectPanel->SetItem( pItem );
+ RecompositeItem();
+ InvalidateLayout();
+ m_pTeamColorNavPanel->UpdateButtonSelectionStates( 0 );
+}
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::SetSpecialAttributesOnly( bool bSpecialOnly )
+{
+ if ( m_pItemNamePanel )
+ {
+ if ( bSpecialOnly )
+ {
+ m_pItemNamePanel->SetNameOnly( false );
+ }
+ m_pItemNamePanel->SetSpecialAttributesOnly( bSpecialOnly );
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::Reset()
+{
+ InvalidateLayout( true, true );
+ SetItem( m_pItemViewData );
+}
+
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::SetItemCopy( CEconItemView *pItem )
+{
+ // Make a copy of SO data since it comes from the market and will fall out of scope
+ if ( m_pItemViewData )
+ {
+ delete m_pItemViewData;
+ m_pItemViewData = NULL;
+ }
+
+ if ( m_pSOEconItemData )
+ {
+ delete m_pSOEconItemData;
+ m_pSOEconItemData = NULL;
+ }
+
+ if ( pItem )
+ {
+ m_pItemViewData = new CEconItemView( *pItem );
+ m_pSOEconItemData = new CEconItem( *pItem->GetSOCData() );
+ m_pItemViewData->SetNonSOEconItem( m_pSOEconItemData );
+ // always use high res for inspect
+ m_pItemViewData->SetWeaponSkinUseHighRes( true );
+ }
+ SetItem( m_pItemViewData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::OnRadioButtonChecked( vgui::Panel *panel )
+{
+ OnCommand( panel->GetName() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::OnNavButtonSelected( KeyValues *pData )
+{
+ const int iTeam = pData->GetInt( "userdata", -1 ); AssertMsg( iTeam >= 0, "Bad filter" );
+ if ( iTeam < 0 )
+ return;
+
+ CEconItemView* pItem = m_pModelInspectPanel->GetItem();
+ if ( pItem )
+ {
+ pItem->SetTeamNumber( iTeam );
+
+ RecompositeItem();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemInspectionPanel::RecompositeItem()
+{
+ // Force a reload of the skin
+ CEconItemView* pItem = m_pModelInspectPanel->GetItem();
+ if ( pItem )
+ {
+#ifdef STAGING_ONLY
+ if ( !tf_paint_kit_force_regen.GetBool() )
+#endif
+ {
+ pItem->CancelWeaponSkinComposite();
+ pItem->SetWeaponSkinBase( NULL );
+ pItem->SetWeaponSkinBaseCompositor( NULL );
+ }
+ }
+}
diff --git a/game/client/tf/vgui/tf_item_inspection_panel.h b/game/client/tf/vgui/tf_item_inspection_panel.h
new file mode 100644
index 0000000..5b57d70
--- /dev/null
+++ b/game/client/tf/vgui/tf_item_inspection_panel.h
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_ITEM_INSPECTION_PANEL_H
+#define TF_ITEM_INSPECTION_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/EditablePanel.h>
+#include "tf_controls.h"
+
+using namespace vgui;
+
+class CEconItemView;
+class CEmbeddedItemModelPanel;
+class CNavigationPanel;
+class CItemModelPanel;
+
+class CTFItemInspectionPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFItemInspectionPanel, EditablePanel )
+public:
+ CTFItemInspectionPanel( Panel* pPanel, const char *pszName );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ void SetItemCopy ( CEconItemView *pItem );
+ void Reset();
+ void SetSpecialAttributesOnly( bool bSpecialOnly );
+
+private:
+ // we always want to copy item with SetItemCopy to make sure that we use high res skin
+ void SetItem( CEconItemView *pItem );
+ void RecompositeItem();
+ CNavigationPanel* m_pTeamColorNavPanel;
+
+ MESSAGE_FUNC_PARAMS( OnNavButtonSelected, "NavButtonSelected", pData );
+ MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel );
+
+ CEmbeddedItemModelPanel *m_pModelInspectPanel;
+ CItemModelPanel *m_pItemNamePanel;
+
+ CEconItemView *m_pItemViewData;
+ CEconItem *m_pSOEconItemData;
+};
+
+#endif // TF_ITEM_INSPECTION_PANEL_H
diff --git a/game/client/tf/vgui/tf_item_pickup_panel.cpp b/game/client/tf/vgui/tf_item_pickup_panel.cpp
new file mode 100644
index 0000000..c26d5bd
--- /dev/null
+++ b/game/client/tf/vgui/tf_item_pickup_panel.cpp
@@ -0,0 +1,384 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "vgui/IInput.h"
+#include <vgui/IVGui.h>
+#include <vgui/IScheme.h>
+#include "tf_item_pickup_panel.h"
+#include "iclientmode.h"
+#include "baseviewport.h"
+#include "econ_entity.h"
+#include "c_baseplayer.h"
+#include "gamestringpool.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/Label.h"
+#include "vgui_controls/Button.h"
+#include "econ_item_system.h"
+#include "ienginevgui.h"
+#include "achievementmgr.h"
+#include "fmtstr.h"
+#include "tf_item_inventory.h"
+#include "item_confirm_delete_dialog.h"
+#include "backpack_panel.h"
+#include "econ_ui.h"
+#include "c_tf_player.h"
+#include "character_info_panel.h"
+
+ConVar tf_explanations_discardpanel( "tf_explanations_discardpanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItemPickupPanel::CTFItemPickupPanel( Panel *parent ) : CItemPickupPanel( parent )
+{
+ m_pClassImage = NULL;
+ m_pClassImageBG = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemPickupPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pClassImage = dynamic_cast<vgui::ImagePanel*>(FindChildByName( "classimage" ));
+ m_pClassImageBG = FindChildByName( "classimageoutline" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemPickupPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "changeloadout" ) )
+ {
+ // We dont want the UI to close -- we're about to change our loadout
+ SetReturnToGame( false );
+ AcknowledgeItems();
+
+ int iClass = TF_CLASS_UNDEFINED;
+ if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() )
+ {
+ if ( m_aItems[m_iSelectedItem].pItem.IsValid() && !m_aItems[m_iSelectedItem].bDiscarded )
+ {
+ // Open the loadout panel with the first class that can use this item (or the base loadout screen if it's an all-class item)
+ if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByAllClasses() )
+ {
+ iClass = TF_CLASS_UNDEFINED;
+ }
+ else
+ {
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByClass(i) )
+ {
+ iClass = -i;
+ break;
+ }
+ }
+
+ if ( iClass == TF_CLASS_UNDEFINED )
+ {
+ // Item's not usable by any class. Go to backpack.
+ iClass = ECONUI_BACKPACK;
+ }
+ }
+ }
+ }
+
+ ShowPanel( false );
+
+ EconUI()->OpenEconUI( iClass, true );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemPickupPanel::UpdateModelPanels( void )
+{
+ BaseClass::UpdateModelPanels();
+
+ if ( m_pClassImage )
+ {
+ m_pClassImage->SetVisible( false );
+ if ( m_pClassImageBG )
+ {
+ m_pClassImageBG->SetVisible( false );
+ }
+ if ( m_aModelPanels[2]->HasItem() )
+ {
+ CEconItemView *pItem = m_aModelPanels[2]->GetItem();
+
+ int iClass = -1;
+ if ( pItem->GetStaticData()->CanBeUsedByAllClasses() )
+ {
+ iClass = TF_CLASS_UNDEFINED;
+ }
+ else
+ {
+ // Find a class that can use the item, and show that class image
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( pItem->GetStaticData()->CanBeUsedByClass(i) )
+ {
+ iClass = i;
+ break;
+ }
+ }
+ }
+
+ if ( iClass != -1 )
+ {
+ m_pClassImage->SetImage( g_pszItemClassImagesRed[iClass] );
+ m_pClassImage->SetVisible( true );
+ if ( m_pClassImageBG )
+ {
+ m_pClassImageBG->SetVisible( true );
+ }
+ }
+ }
+ }
+
+ // Update the loadout button as appropriate
+ if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() )
+ {
+ bool bDiscarded = false;
+ if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() )
+ {
+ bDiscarded = m_aItems[m_iSelectedItem].bDiscarded;
+ }
+
+ // Open the loadout panel with the first class that can use this item
+ if ( m_aItems[m_iSelectedItem].pItem.IsValid() && !bDiscarded )
+ {
+ if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByAllClasses() )
+ {
+ SetDialogVariable("loadouttext", g_pVGuiLocalize->Find( "#OpenGeneralLoadout" ) );
+ }
+ else
+ {
+ int iClass = TF_CLASS_UNDEFINED;
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( m_aItems[m_iSelectedItem].pItem.GetStaticData()->CanBeUsedByClass(i) )
+ {
+ iClass = i;
+ break;
+ }
+ }
+
+ if ( iClass != TF_CLASS_UNDEFINED )
+ {
+ wchar_t wzLocalized[128];
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#OpenSpecificLoadout" ), 1, g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) );
+ SetDialogVariable("loadouttext", wzLocalized );
+ }
+ else
+ {
+ SetDialogVariable("loadouttext", g_pVGuiLocalize->Find( "#OpenBackpack" ) );
+ m_pOpenLoadoutButton->SetVisible( true );
+ }
+ }
+ }
+ }
+}
+
+static vgui::DHANDLE<CTFItemPickupPanel> g_TFItemPickupPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItemPickupPanel *OpenTFItemPickupPanel( void )
+{
+ if (!g_TFItemPickupPanel.Get())
+ {
+ g_TFItemPickupPanel = vgui::SETUP_PANEL( new CTFItemPickupPanel( NULL ) );
+ g_TFItemPickupPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_TFItemPickupPanel->ShowPanel( true );
+
+ return g_TFItemPickupPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItemPickupPanel *GetTFItemPickupPanel( void )
+{
+ return g_TFItemPickupPanel.Get();
+}
+
+//=======================================================================================================================================================
+// ITEM DISCARD PANEL
+//=======================================================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItemDiscardPanel::CTFItemDiscardPanel( Panel *parent ) : CItemDiscardPanel( parent )
+{
+ m_flStartExplanationsAt = 0;
+ m_pExplanationALabel = NULL;
+ m_pExplanationBLabel = NULL;
+ m_pExplanationCaratLabel = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDiscardPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pExplanationALabel = dynamic_cast<vgui::Label*>( FindChildByName("ExplanationLabel") );
+ m_pExplanationBLabel = dynamic_cast<vgui::Label*>( FindChildByName("ExplanationLabel2") );
+ m_pExplanationCaratLabel = dynamic_cast<vgui::Label*>( FindChildByName("CaratLabel2") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDiscardPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ m_pExplanationALabel->SetVisible( !m_bDiscardedNewItem && !m_bMadeRoom );
+ m_pExplanationBLabel->SetVisible( !m_bDiscardedNewItem && !m_bMadeRoom );
+ m_pExplanationCaratLabel->SetVisible( !m_bDiscardedNewItem && !m_bMadeRoom );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDiscardPanel::ShowPanel(bool bShow)
+{
+ BaseClass::ShowPanel( bShow );
+
+ if ( bShow )
+ {
+ if ( !tf_explanations_discardpanel.GetBool() )
+ {
+ m_flStartExplanationsAt = engine->Time() + 0.5;
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDiscardPanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( m_flStartExplanationsAt && m_flStartExplanationsAt < engine->Time() && TFModalStack()->IsEmpty() )
+ {
+ m_flStartExplanationsAt = 0;
+
+ tf_explanations_discardpanel.SetValue( 1 );
+
+ CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ }
+
+ if ( !m_flStartExplanationsAt )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFItemDiscardPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "show_explanations" ) )
+ {
+ if ( !m_flStartExplanationsAt )
+ {
+ m_flStartExplanationsAt = engine->Time();
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+ RequestFocus();
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+#if defined(DEBUG)
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Test_ItemPickupPanel( const CCommand &args )
+{
+ int iClass = TF_CLASS_PYRO;
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer )
+ {
+ iClass = pLocalPlayer->GetPlayerClass()->GetClassIndex();
+ }
+
+ CItemPickupPanel *pItemPanel = EconUI()->OpenItemPickupPanel();
+ pItemPanel->InvalidateLayout( false, true );
+
+ for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
+ {
+ CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
+ if ( pItem && pItem->IsValid() )
+ {
+ pItemPanel->AddItem( pItem );
+ }
+ }
+
+ pItemPanel->DebugRandomizePickupMethods();
+}
+ConCommand test_itempickuppanel( "test_itempickuppanel", Test_ItemPickupPanel, "Debugging tool to test the item pickup panel. Usage: test_itempickuppanel\n", FCVAR_CHEAT );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Test_ItemDiscardPanel( const CCommand &args )
+{
+ int iClass = TF_CLASS_PYRO;
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer )
+ {
+ iClass = pLocalPlayer->GetPlayerClass()->GetClassIndex();
+ }
+
+ CItemDiscardPanel *pItemPanel = EconUI()->OpenItemDiscardPanel();
+ pItemPanel->InvalidateLayout( false, true );
+
+ CEconItemView *pItemView = NULL;
+
+ bool bAllItems = (args.ArgC() <= 1);
+ for ( int i = bAllItems ? 0 : clamp( atoi(args[1]), 0, 2 ); i <= 2 && !pItemView; i++ )
+ {
+ pItemView = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
+ }
+
+ if ( pItemView )
+ {
+ pItemPanel->SetItem( pItemView );
+ }
+}
+
+ConCommand test_itemdiscardpanel( "test_itemdiscardpanel", Test_ItemDiscardPanel, "Debugging tool to test the item discard panel. Usage: test_itemdiscardpanel <weapon name>\n <weapon id>: 0 = primary, 1 = secondary, 2 = melee.", FCVAR_CHEAT );
+#endif // defined(DEBUG)
+
diff --git a/game/client/tf/vgui/tf_item_pickup_panel.h b/game/client/tf/vgui/tf_item_pickup_panel.h
new file mode 100644
index 0000000..8c8728d
--- /dev/null
+++ b/game/client/tf/vgui/tf_item_pickup_panel.h
@@ -0,0 +1,58 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_ITEM_PICKUP_PANEL_H
+#define TF_ITEM_PICKUP_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "item_pickup_panel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFItemPickupPanel : public CItemPickupPanel
+{
+ DECLARE_CLASS_SIMPLE( CTFItemPickupPanel, CItemPickupPanel );
+public:
+ CTFItemPickupPanel( Panel *parent );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+
+protected:
+ virtual void UpdateModelPanels( void );
+
+private:
+ vgui::ImagePanel *m_pClassImage;
+ vgui::Panel *m_pClassImageBG;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFItemDiscardPanel : public CItemDiscardPanel
+{
+ DECLARE_CLASS_SIMPLE( CTFItemDiscardPanel, CItemDiscardPanel );
+public:
+ CTFItemDiscardPanel( Panel *parent );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void ShowPanel( bool bShow );
+ virtual void OnTick( void );
+ virtual void OnCommand( const char *command );
+
+private:
+ vgui::Label *m_pExplanationALabel;
+ vgui::Label *m_pExplanationBLabel;
+ vgui::Label *m_pExplanationCaratLabel;
+ float m_flStartExplanationsAt;
+};
+
+#endif // TF_ITEM_PICKUP_PANEL_H
diff --git a/game/client/tf/vgui/tf_layeredmappanel.cpp b/game/client/tf/vgui/tf_layeredmappanel.cpp
new file mode 100644
index 0000000..65fd609
--- /dev/null
+++ b/game/client/tf/vgui/tf_layeredmappanel.cpp
@@ -0,0 +1,343 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_layeredmappanel.h"
+
+using namespace vgui;
+
+//=========================================================
+// CLayeredMapToolTip
+//=========================================================
+CLayeredMapToolTip::CLayeredMapToolTip(vgui::Panel *parent, const char *text ) : vgui::BaseTooltip( parent, text )
+{
+ m_hCurrentPanel = NULL;
+}
+
+//=========================================================
+void CLayeredMapToolTip::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( !ShouldLayout() )
+ return;
+
+ CTFLayeredMapItemPanel *pMapItemPanel = m_hCurrentPanel.Get();
+ if ( !pMapItemPanel )
+ return;
+
+ m_pControlledPanel->SetVisible( false );
+
+ int x,y;
+ pMapItemPanel->GetPos( x, y );
+
+ int iXPos = 0;
+ int iYPos = 0;
+
+ // Loop through the positions in our strategy, and hope we find a valid spot
+ for ( int i = 0; i < NUM_POSITIONS_PER_STRATEGY; i++ )
+ {
+ itempanel_tooltippos_t iPos = g_iTooltipStrategies[IPTTP_TOP_SIDE][i];
+ GetPosition( iPos, pMapItemPanel, x, y, &iXPos, &iYPos );
+
+ if ( ValidatePosition( pMapItemPanel, x, y, &iXPos, &iYPos ) )
+ break;
+ }
+
+ m_pControlledPanel->SetPos( iXPos, iYPos );
+ m_pControlledPanel->SetVisible( true );
+}
+
+//=========================================================
+void CLayeredMapToolTip::ShowTooltip( vgui::Panel *currentPanel )
+{
+ // Set Data on visible panel
+ if ( !m_pControlledPanel )
+ return;
+
+ if ( currentPanel != m_hCurrentPanel.Get() )
+ {
+ CTFLayeredMapItemPanel *pMapItemPanel = dynamic_cast<CTFLayeredMapItemPanel*>( currentPanel );
+ m_hCurrentPanel.Set( pMapItemPanel );
+ m_pControlledPanel->SetVisible( false );
+ if ( pMapItemPanel )
+ {
+ KeyValues *pData = pMapItemPanel->GetItemKvData();
+ if ( pData )
+ {
+ m_pControlledPanel->SetDialogVariable( "tooltipdescription", pData->GetString( "name", "No Name" ) );
+ }
+ }
+ }
+ BaseClass::ShowTooltip( currentPanel );
+}
+
+//=========================================================
+void CLayeredMapToolTip::HideTooltip()
+{
+ if ( !m_pControlledPanel )
+ return;
+
+ m_pControlledPanel->SetVisible( false );
+ m_hCurrentPanel = NULL;
+}
+
+//=========================================================
+void CLayeredMapToolTip::SetupPanels( CTFLayeredMapPanel *pParentPanel, vgui::EditablePanel *pControlledPanel )
+{
+ m_pParentPanel = pParentPanel;
+ m_pControlledPanel = pControlledPanel;
+}
+
+//=========================================================
+void CLayeredMapToolTip::GetPosition( itempanel_tooltippos_t iTooltipPosition, CTFLayeredMapItemPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos )
+{
+ switch ( iTooltipPosition )
+ {
+ case IPTTP_LEFT:
+ *iXPos = (iItemX - m_pControlledPanel->GetWide() + XRES(18));
+ *iYPos = iItemY - YRES(7);
+ break;
+ case IPTTP_RIGHT:
+ *iXPos = (iItemX + pItemPanel->GetWide() - XRES(20));
+ *iYPos = iItemY - YRES(7);
+ break;
+ case IPTTP_LEFT_CENTERED:
+ *iXPos = (iItemX - m_pControlledPanel->GetWide()) - XRES(4);
+ *iYPos = (iItemY - (m_pControlledPanel->GetTall() * 0.5));
+ break;
+ case IPTTP_RIGHT_CENTERED:
+ *iXPos = (iItemX + pItemPanel->GetWide()) + XRES(4);
+ *iYPos = (iItemY - (m_pControlledPanel->GetTall() * 0.5));
+ break;
+ case IPTTP_ABOVE:
+ *iXPos = (iItemX + (pItemPanel->GetWide() * 0.5)) - (m_pControlledPanel->GetWide() * 0.5);
+ *iYPos = (iItemY - m_pControlledPanel->GetTall() - YRES(4));
+ break;
+ case IPTTP_BELOW:
+ *iXPos = (iItemX + (pItemPanel->GetWide() * 0.5)) - (m_pControlledPanel->GetWide() * 0.5);
+ *iYPos = (iItemY + pItemPanel->GetTall() + YRES(4));
+ break;
+ }
+}
+
+//=========================================================
+bool CLayeredMapToolTip::ValidatePosition( CTFLayeredMapItemPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos )
+{
+ bool bSucceeded = true;
+
+ // Make sure the popup stays onscreen.
+ if ( *iXPos < 0 )
+ {
+ *iXPos = 0;
+ }
+ else if ( (*iXPos + m_pControlledPanel->GetWide()) > m_pParentPanel->GetWide() )
+ {
+ int iXPosNew = m_pParentPanel->GetWide() - m_pControlledPanel->GetWide();
+ // make sure it is still on the screen
+ if ( iXPosNew >= 0 )
+ {
+ *iXPos = iXPosNew;
+ }
+ else
+ {
+ bSucceeded = false;
+ }
+ }
+
+ if ( *iYPos < 0 )
+ {
+ *iYPos = 0;
+ }
+ else if ( (*iYPos + m_pControlledPanel->GetTall() + YRES(32)) > m_pParentPanel->GetTall() )
+ {
+ // Move it up above our item
+ int iYPosNew = iItemY - m_pControlledPanel->GetTall() - YRES(4);
+ // make sure it is still on the screen
+ if ( iYPosNew >= 0 )
+ {
+ *iYPos = iYPosNew;
+ }
+ else
+ {
+ bSucceeded = false;
+ }
+ }
+
+ if ( bSucceeded )
+ {
+ // We also fail if moving it to keep it on screen moved it over the item panel itself
+ Vector2D vecToolTipMin, vecToolTipMax, vecItemMin, vecItemMax;
+ vecToolTipMin.x = *iXPos;
+ vecToolTipMin.y = *iYPos;
+ vecToolTipMax.x = vecToolTipMin.x + m_pControlledPanel->GetWide();
+ vecToolTipMax.y = vecToolTipMin.y + m_pControlledPanel->GetTall();
+
+ vecItemMin.x = iItemX;
+ vecItemMin.y = iItemY;
+ vecItemMax.x = vecItemMin.x + m_hCurrentPanel->GetWide();
+ vecItemMax.y = vecItemMin.y + m_hCurrentPanel->GetTall();
+
+ bSucceeded = !( vecToolTipMin.x < vecItemMax.x && vecToolTipMax.x > vecItemMin.x && vecToolTipMin.y < vecItemMax.y && vecToolTipMax.y > vecItemMin.y );
+ }
+
+ return bSucceeded;
+}
+
+//-----------------------------------------------------------------------------
+// CTFLayeredImagePanelItem
+//-----------------------------------------------------------------------------
+DECLARE_BUILD_FACTORY( CTFLayeredMapItemPanel );
+
+CTFLayeredMapItemPanel::CTFLayeredMapItemPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName )
+{
+ m_bIsCompleted = false;
+ m_bIsMouseOvered = false;
+
+ m_kvData = NULL;
+}
+
+//-----------------------------------------------------------------------------
+void CTFLayeredMapItemPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // load control settings...
+ LoadControlSettings( "resource/UI/LayeredMapPanelItem.res" );
+
+ // get References to the 4 images
+ m_pIsCompleted = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("IsCompletedImage") );
+ m_pIsCompletedHighlight = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("IsCompletedHighlight") );
+
+ m_pNotCompleted = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("NotCompletedImage") );
+ m_pNotCompletedHighlight = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("NotCompletedHighlight") );
+
+ m_pIsCompleted->SetVisible( true );
+ m_pIsCompleted->SetMouseInputEnabled( false );
+ m_pIsCompletedHighlight->SetVisible( false );
+ m_pIsCompletedHighlight->SetMouseInputEnabled( false );
+ m_pNotCompleted->SetVisible( false );
+ m_pNotCompleted->SetMouseInputEnabled( false );
+ m_pNotCompletedHighlight->SetVisible( false );
+ m_pNotCompletedHighlight->SetMouseInputEnabled( false );
+
+ SetMouseInputEnabled( true );
+}
+
+//-----------------------------------------------------------------------------
+void CTFLayeredMapItemPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+ // Store Information needed for a mouse over
+
+ KeyValues *kvData = inResourceData->FindKey( "mapitem_kv" );
+ if ( kvData )
+ {
+ if ( m_kvData )
+ {
+ m_kvData->deleteThis();
+ }
+ m_kvData = new KeyValues( "mapitem_kv" );
+ kvData->CopySubkeys( m_kvData );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFLayeredMapItemPanel::OnCursorEntered( void )
+{
+ BaseClass::OnCursorEntered();
+ m_pIsCompletedHighlight->SetVisible( m_bIsCompleted );
+ m_pNotCompletedHighlight->SetVisible( !m_bIsCompleted );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFLayeredMapItemPanel::OnCursorExited( void )
+{
+ BaseClass::OnCursorExited();
+ m_pIsCompletedHighlight->SetVisible( false );
+ m_pNotCompletedHighlight->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFLayeredMapItemPanel::OnMousePressed(vgui::MouseCode code)
+{
+ SetCompletionState( !m_bIsCompleted );
+}
+
+//-----------------------------------------------------------------------------
+void CTFLayeredMapItemPanel::SetCompletionState( bool bIsCompleted )
+{
+ m_bIsCompleted = bIsCompleted;
+ m_pIsCompleted->SetVisible( m_bIsCompleted );
+ m_pNotCompleted->SetVisible( !m_bIsCompleted );
+}
+
+//-----------------------------------------------------------------------------
+// CTFLayeredMapPanel
+// - MultiImage Panel that places all images on top of each other.
+// An external manager may then individually toggle each image on or off
+// Each image is sized to fit the panel
+//-----------------------------------------------------------------------------
+DECLARE_BUILD_FACTORY( CTFLayeredMapPanel );
+
+CTFLayeredMapPanel::CTFLayeredMapPanel( Panel *parent, const char *pName ) : vgui::EditablePanel( parent, pName )
+{
+ m_pLayeredMapKv = NULL;
+}
+
+//-----------------------------------------------------------------------------
+void CTFLayeredMapPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // load control settings...
+ LoadControlSettings( "resource/UI/LayeredMapPanel.res" );
+
+ m_MapItems.PurgeAndDeleteElements();
+
+ // Get ToolTip
+ m_pToolTipPanel = dynamic_cast<EditablePanel*>( FindChildByName( "ToolTipPanel" ) );
+ m_pToolTip = new CLayeredMapToolTip( this );
+ m_pToolTip->SetupPanels( this, m_pToolTipPanel );
+
+ if ( m_pLayeredMapKv )
+ {
+ int iItemCount = m_pLayeredMapKv->GetInt( "item_count", 0 );
+ for ( int i = 0; i < iItemCount; ++i )
+ {
+ CTFLayeredMapItemPanel *pItem = dynamic_cast<CTFLayeredMapItemPanel*>( FindChildByName( VarArgs( "MapItem%d", i ) ) );
+ if ( pItem )
+ {
+ pItem->SetTooltip( m_pToolTip, "" );
+ m_MapItems.AddToTail( pItem );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFLayeredMapPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ if ( inResourceData )
+ {
+ if ( m_pLayeredMapKv )
+ {
+ m_pLayeredMapKv->deleteThis();
+ }
+ m_pLayeredMapKv = new KeyValues( "layeredMapPanel_kv" );
+ inResourceData->CopySubkeys( m_pLayeredMapKv );
+ }
+}
+
diff --git a/game/client/tf/vgui/tf_layeredmappanel.h b/game/client/tf/vgui/tf_layeredmappanel.h
new file mode 100644
index 0000000..e3418fc
--- /dev/null
+++ b/game/client/tf/vgui/tf_layeredmappanel.h
@@ -0,0 +1,106 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Layered Map that contains a background and stateful locations.
+// Each location will support mouse over information
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_LAYEREDMAPPANEL_H
+#define TF_LAYEREDMAPPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui/IScheme.h>
+#include <vgui_controls/EditablePanel.h>
+#include <vgui_controls/ScalableImagePanel.h>
+#include <vgui_controls/Tooltip.h>
+#include "item_model_panel.h"
+
+class CTFLayeredMapPanel;
+class CTFLayeredMapItemPanel;
+
+//=========================================================
+class CLayeredMapToolTip : public vgui::BaseTooltip
+{
+ DECLARE_CLASS_SIMPLE( CLayeredMapToolTip, vgui::BaseTooltip );
+public:
+ CLayeredMapToolTip(vgui::Panel *parent, const char *text = NULL);
+
+ void SetText(const char *text) { return; }
+ const char *GetText() { return NULL; }
+
+ virtual void PerformLayout();
+ virtual void ShowTooltip( vgui::Panel *currentPanel );
+ virtual void HideTooltip();
+
+ void SetupPanels( CTFLayeredMapPanel *pParentPanel, vgui::EditablePanel *pControlledPanel );
+
+private:
+ void GetPosition( itempanel_tooltippos_t iTooltipPosition, CTFLayeredMapItemPanel *pMapItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos );
+ bool ValidatePosition( CTFLayeredMapItemPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos );
+
+ vgui::DHANDLE<CTFLayeredMapItemPanel> m_hCurrentPanel;
+
+ CTFLayeredMapPanel *m_pParentPanel;
+ vgui::EditablePanel *m_pControlledPanel;
+};
+
+//=========================================================
+class CTFLayeredMapItemPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFLayeredMapItemPanel, vgui::EditablePanel );
+public:
+ CTFLayeredMapItemPanel( Panel *parent, const char *pName );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ // Button
+ virtual void OnCursorEntered();
+ virtual void OnCursorExited();
+ virtual void OnMousePressed(vgui::MouseCode code);
+
+ //
+ void SetCompletionState( bool bIsCompleted );
+ KeyValues * GetItemKvData () { return m_kvData; }
+
+private:
+
+ vgui::ScalableImagePanel *m_pIsCompleted;
+ vgui::ScalableImagePanel *m_pIsCompletedHighlight;
+
+ vgui::ScalableImagePanel *m_pNotCompleted;
+ vgui::ScalableImagePanel *m_pNotCompletedHighlight;
+
+ bool m_bIsCompleted;
+ bool m_bIsMouseOvered;
+
+ KeyValues *m_kvData;
+};
+
+//=========================================================
+class CTFLayeredMapPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFLayeredMapPanel, vgui::EditablePanel );
+public:
+ CTFLayeredMapPanel( Panel *parent, const char *pName );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+
+ //CUtlVector<vgui::ImagePanel*> m_pImages;
+
+private:
+
+ EditablePanel *m_pToolTipPanel;
+ CLayeredMapToolTip *m_pToolTip;
+
+ KeyValues *m_pLayeredMapKv;
+
+ CUtlVector<CTFLayeredMapItemPanel*> m_MapItems;
+};
+
+#endif // TF_LAYEREDIMAGEPANEL_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_leaderboardpanel.cpp b/game/client/tf/vgui/tf_leaderboardpanel.cpp
new file mode 100644
index 0000000..2e0327d
--- /dev/null
+++ b/game/client/tf/vgui/tf_leaderboardpanel.cpp
@@ -0,0 +1,103 @@
+#include "cbase.h"
+#include "tf_leaderboardpanel.h"
+#include "econ_controls.h"
+#include "tf_asyncpanel.h"
+#include "tf_mapinfo.h"
+#include "vgui_avatarimage.h"
+#include "tf_item_inventory.h"
+
+
+CTFLeaderboardPanel::CTFLeaderboardPanel( Panel *pParent, const char *pszPanelName )
+ : CBaseASyncPanel( pParent, pszPanelName )
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create leaderboard panels
+//-----------------------------------------------------------------------------
+void CTFLeaderboardPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/econ/LeaderboardPanel.res" );
+}
+
+void CTFLeaderboardPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ m_EvenTextColor = GetSchemeColor( inResourceData->GetString( "EvenTextColor" ), pScheme);
+ m_OddTextColor = GetSchemeColor( inResourceData->GetString( "OddTextColor" ), pScheme);
+ m_LocalPlayerTextColor = GetSchemeColor( inResourceData->GetString( "LocalPlayerTextColor" ), pScheme);
+
+ EditablePanel *pScoresContainer = dynamic_cast< EditablePanel* >( FindChildByName( "ScoresContainer", true ) );
+
+ if ( pScoresContainer )
+ {
+ m_vecLeaderboardEntries.Purge();
+ for ( int i = 0; i < 7; ++ i )
+ {
+ vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( pScoresContainer, "LeaderboardEntry" );
+ pEntryUI->ApplySchemeSettings( pScheme );
+ pEntryUI->LoadControlSettings( "Resource/UI/LeaderboardSpreadEntry.res" );
+ m_vecLeaderboardEntries.AddToTail( pEntryUI );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check for leaderboard data
+//-----------------------------------------------------------------------------
+bool CTFLeaderboardPanel::CheckForData_Internal()
+{
+ return UpdateLeaderboards();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks if we have friends leaderboard data downloaded. If so, sets
+// the data into the panels
+//-----------------------------------------------------------------------------
+bool CTFLeaderboardPanel::UpdateLeaderboards()
+{
+ CUtlVector< LeaderboardEntry_t* > scores;
+ if ( !GetLeaderboardData( scores ) )
+ return false;
+
+ int x=0,y=0;
+ FOR_EACH_VEC( m_vecLeaderboardEntries, i )
+ {
+ Color colorToUse = i % 2 == 1 ? m_OddTextColor : m_EvenTextColor;
+ EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] );
+ if ( pContainer )
+ {
+ bool bIsEntryVisible = i < scores.Count();
+ pContainer->SetVisible( bIsEntryVisible );
+ pContainer->SetPos( x, y );
+ y += m_yEntryStep;
+ if ( bIsEntryVisible )
+ {
+ const LeaderboardEntry_t* leaderboardEntry = scores[i];
+ const CSteamID &steamID = leaderboardEntry->m_steamIDUser;
+ bool bIsLocalPlayer = steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUser()->GetSteamID() == steamID;
+ pContainer->SetDialogVariable( "rank", leaderboardEntry->m_nGlobalRank );
+ pContainer->SetDialogVariable( "username", InventoryManager()->PersonaName_Get( steamID.GetAccountID() ) );
+ pContainer->SetDialogVariable( "score", leaderboardEntry->m_nScore );
+
+ CExLabel *pText = dynamic_cast< CExLabel* >( pContainer->FindChildByName( "UserName" ) );
+ if ( pText )
+ {
+ pText->SetColorStr( bIsLocalPlayer ? m_LocalPlayerTextColor : colorToUse );
+ }
+
+ CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) );
+ if ( pAvatar )
+ {
+ pAvatar->SetShouldDrawFriendIcon( false );
+ pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 );
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/game/client/tf/vgui/tf_leaderboardpanel.h b/game/client/tf/vgui/tf_leaderboardpanel.h
new file mode 100644
index 0000000..8420d84
--- /dev/null
+++ b/game/client/tf/vgui/tf_leaderboardpanel.h
@@ -0,0 +1,36 @@
+#ifndef TF_LEADERBOARDPANEL_H
+#define TF_LEADERBOARDPANEL_H
+
+#include "vgui_controls/EditablePanel.h"
+#include "tf_wardata.h"
+#include "vgui_controls/ProgressBar.h"
+#include "quest_log_panel.h"
+#include "tf_asyncpanel.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFLeaderboardPanel : public CBaseASyncPanel
+{
+ DECLARE_CLASS_SIMPLE( CTFLeaderboardPanel, CBaseASyncPanel );
+public:
+ CTFLeaderboardPanel( Panel *pParent, const char *pszPanelName );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData );
+
+protected:
+ virtual bool GetLeaderboardData( CUtlVector< LeaderboardEntry_t* >& scores ) = 0;
+ virtual bool UpdateLeaderboards();
+ virtual bool CheckForData_Internal() OVERRIDE;
+
+ CUtlVector< EditablePanel* > m_vecLeaderboardEntries;
+
+ CPanelAnimationVarAliasType( int, m_yEntryStep, "entry_step", "5", "proportional_int");
+ Color m_EvenTextColor;
+ Color m_OddTextColor;
+ Color m_LocalPlayerTextColor;
+};
+
+#endif //TF_LEADERBOARDPANEL_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_lobby_container_frame.cpp b/game/client/tf/vgui/tf_lobby_container_frame.cpp
new file mode 100644
index 0000000..b8d0e48
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame.cpp
@@ -0,0 +1,630 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "ienginevgui.h"
+#include "vgui/IVGui.h"
+#include "vgui/IInput.h"
+#include "vgui_controls/MenuItem.h"
+#include "vgui_controls/PropertySheet.h"
+
+#include "tf_party.h"
+#include "tf_lobbypanel.h"
+#include "tf_pvp_rank_panel.h"
+
+#include "tf_lobby_container_frame.h"
+#include "tf_controls.h"
+
+#include "tf_item_inventory.h"
+#include "tf_lobbypanel.h"
+#include "tf_hud_mainmenuoverride.h"
+#include "tf_matchmaking_dashboard.h"
+#include "tf_ping_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+bool BIsPartyLeader()
+{
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ return ( pParty == NULL || pParty->GetLeader() == steamapicontext->SteamUser()->GetSteamID() );
+}
+
+bool BIsPartyInUIState()
+{
+ if ( !GCClientSystem()->BConnectedtoGC() )
+ return false;
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ return ( pParty == NULL || pParty->GetState() == CSOTFParty_State_UI );
+}
+
+CSteamID SteamIDFromDecimalString( const char *pszUint64InDecimal )
+{
+ uint64 ulSteamID = 0;
+ if ( sscanf( pszUint64InDecimal, "%llu", &ulSteamID ) )
+ {
+ return CSteamID( ulSteamID );
+ }
+ else
+ {
+ Assert( false );
+ return CSteamID();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseLobbyContainerFrame::CBaseLobbyContainerFrame( const char* pszPanelName )
+ : vgui::PropertyDialog( NULL, pszPanelName )
+ , m_bNextButtonEnabled( false )
+ , m_pContextMenu( NULL )
+{
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ SetMoveable( false );
+ SetSizeable( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ ListenForGameEvent( "lobby_updated" );
+ ListenForGameEvent( "party_updated" );
+ ListenForGameEvent( "client_beginconnect" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseLobbyContainerFrame::~CBaseLobbyContainerFrame( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // load control settings...
+ LoadControlSettings( GetResFile() );
+
+ m_pStartPartyButton = dynamic_cast<vgui::Button *>(FindChildByName( "StartPartyButton", true )); Assert( m_pStartPartyButton );
+ m_pBackButton = dynamic_cast<vgui::Button *>( FindChildByName( "BackButton", true ) ); Assert( m_pBackButton );
+ m_pNextButton = dynamic_cast<vgui::Button *>( FindChildByName( "NextButton", true ) ); Assert( m_pNextButton );
+
+ SetOKButtonVisible(false);
+ SetCancelButtonVisible(false);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::ShowPanel(bool bShow)
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // Keep the MM dashboard on top of us
+ bShow ? GetMMDashboardParentManager()->PushModalFullscreenPopup( this )
+ : GetMMDashboardParentManager()->PopModalFullscreenPopup( this );
+
+ m_pContents->SetControlVisible( "PartyActiveGroupBox", false );
+
+ // Make sure we're signed on
+ if ( bShow )
+ {
+ if ( GetPropertySheet()->GetActivePage() != m_pContents )
+ {
+ GetPropertySheet()->SetActivePage( m_pContents );
+ }
+ else
+ {
+ // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it
+ vgui::ivgui()->PostMessage( m_pContents->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() );
+ }
+
+ Activate();
+
+ // I don't know why, I don't want to know why, I shouldn't
+ // have to wonder why, but for whatever reason this stupid
+ // panel isn't laying out correctly unless we do this terribleness
+ InvalidateLayout( true );
+ m_pContents->InvalidateLayout( true, true );
+
+ GTFGCClientSystem()->SetLocalPlayerSquadSurplus( false );
+ WriteControls();
+ m_pContents->UpdateControls();
+
+ Panel* pPvPRankPanel = FindChildByName( "RankPanel", true );
+ if ( pPvPRankPanel )
+ {
+ pPvPRankPanel->OnCommand( "update_base_state" );
+ pPvPRankPanel->OnCommand( "begin_xp_lerp" );
+ }
+ }
+ else
+ {
+ if ( m_hPingPanel )
+ {
+ m_hPingPanel->MarkForDeletion();
+ }
+ }
+
+ OnCommand( "leave_party" );
+
+ SetVisible( bShow );
+ m_pContents->SetVisible( bShow );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::SetNextButtonEnabled( bool bValue )
+{
+ m_bNextButtonEnabled = bValue;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::OnThink()
+{
+ BaseClass::OnThink();
+
+ // Check if we don't want to be here, then get out!
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ Msg( "Hiding LobbyContainerFrame" );
+ ShowPanel(false);
+ return;
+ }
+
+ WriteControls();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::FireGameEvent( IGameEvent *event )
+{
+ if ( GTFGCClientSystem()->GetSearchMode() != GetHandledMode() )
+ return;
+
+ const char *pszEventname = event->GetName();
+ if ( !Q_stricmp( pszEventname, "lobby_updated" ) || !Q_stricmp( pszEventname, "party_updated" ) )
+ {
+ WriteControls();
+ return;
+ }
+
+ // Bail when we connect to any server
+ if ( !Q_stricmp( pszEventname, "client_beginconnect" ) )
+ {
+ ShowPanel( false );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::StartSearch( void )
+{
+ // Is anyone banned from matchmaking?
+ RTime32 rtimeExpire = 0;
+ if ( m_pContents->IsAnyoneBanned( rtimeExpire ) )
+ {
+ CRTime timeExpire( rtimeExpire );
+ timeExpire.SetToGMT( false );
+ char out_buf[k_RTimeRenderBufferSize];
+ wchar_t wszExpire[512];
+ wchar_t wszLocalized[512];
+ g_pVGuiLocalize->ConvertANSIToUnicode( timeExpire.Render( out_buf ), wszExpire, sizeof( wszExpire ) );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Banned" ), 1, wszExpire );
+ ShowMessageBox( "#TF_Matchmaking_Title", wszLocalized, "#GameUI_OK" );
+ return;
+ }
+
+ if ( VerifyPartyAuthorization() )
+ {
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "options" ) )
+ {
+ OpenOptionsContextMenu();
+ }
+ else if ( FStrEq( command, "back" ) )
+ {
+ if ( !GCClientSystem()->BConnectedtoGC() || !BIsPartyLeader() )
+ {
+ // TODO: Remove this when we have the dashboard everywhere.
+ // Well...this entire panel should be gone.
+ GTFGCClientSystem()->EndMatchmaking();
+ // And hide us
+ ShowPanel( false );
+ return;
+ }
+
+ HandleBackPressed();
+ }
+ else if ( FStrEq( command, "leave_party" ) )
+ {
+ m_pStartPartyButton->SetVisible( true );
+ SetControlVisible( "PlayWithFriendsExplanation", true );
+ m_pContents->SetControlVisible( "PartyActiveGroupBox", false );
+ }
+ else if ( FStrEq( command, "start_party" ) )
+ {
+ m_pStartPartyButton->SetVisible( false );
+ SetControlVisible( "PlayWithFriendsExplanation", false );
+ m_pContents->SetControlVisible( "PartyActiveGroupBox", true );
+
+ Assert( steamapicontext );
+
+ IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_join" );
+ if ( pEvent )
+ {
+ pEvent->SetString( "steamid", CFmtStr( "%llu", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() ) );
+ pEvent->SetInt( "solo", 1 );
+ gameeventmanager->FireEventClientSide( pEvent );
+ }
+ }
+ else
+ {
+
+ // What other commands are there?
+ Assert( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void LeaveSearch( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ switch ( GTFGCClientSystem()->GetSearchMode() )
+ {
+ case TF_Matchmaking_MVM:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+ break;
+
+ case TF_Matchmaking_LADDER:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
+ break;
+
+ default:
+ AssertMsg1( false, "Unknown search mode %d", (int)GTFGCClientSystem()->GetSearchMode() );
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE )
+ {
+ if ( m_pContents->IsPartyActiveGroupBoxVisible() )
+ {
+ ShowConfirmDialog( "#TF_MM_LeaveParty_Title", "#TF_MM_LeaveParty_Confirm",
+ "#TF_Coach_Yes", "#TF_Coach_No",
+ &CBaseLobbyContainerFrame::LeaveLobbyPanel );
+ return;
+ }
+ else if ( GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_SEARCHING )
+ {
+ ShowConfirmDialog( "#TF_MM_LeaveQueue_Title", "#TF_MM_LeaveQueue_Confirm",
+ "#TF_Coach_Yes", "#TF_Coach_No",
+ &LeaveSearch );
+ return;
+ }
+
+ OnCommand( "back" );
+ return;
+ }
+
+ BaseClass::OnKeyCodeTyped( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::OnKeyCodePressed(vgui::KeyCode code)
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B || nButtonCode == STEAMCONTROLLER_START )
+ {
+ OnCommand( "back" );
+ return;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_X )
+ {
+ m_pContents->ToggleJoinLateCheckButton();
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::WriteControls()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !IsVisible() )
+ return;
+
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+
+ bool bNoGC = false;
+ if ( !GCClientSystem()->BConnectedtoGC() ||
+ GTFGCClientSystem()->BHaveLiveMatch() ||
+ ( GTFGCClientSystem()->GetParty() && GTFGCClientSystem()->GetParty()->BOffline() ) )
+ {
+ bNoGC = true;
+ }
+
+ SetControlVisible( "PlayWithFriendsExplanation", !bNoGC && ShouldShowPartyButton(), true );
+ SetControlVisible( "RankPanel", !bNoGC, true );
+ SetControlVisible( "NoGCGroupBox", bNoGC, true );
+
+ GetPropertySheet()->SetTabWidth( -1 );
+
+ // Check if we already have a party, then make sure and show it
+ if ( !m_pStartPartyButton->IsVisible() && m_pContents->NumPlayersInParty() > 1 )
+ {
+ m_pContents->SetControlVisible( "PartyActiveGroupBox", true );
+ }
+
+ // Show/hide start party button as appropriate
+ bool bShowPartyButton = ShouldShowPartyButton();
+
+ m_pStartPartyButton->SetVisible( bShowPartyButton && !bNoGC );
+
+ // Check for matchmaking bans and display time remaining if we're banned
+ if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
+ return;
+
+ CEconGameAccountClient *pGameAccountClient = NULL;
+ if ( InventoryManager() && TFInventoryManager()->GetLocalTFInventory() && TFInventoryManager()->GetLocalTFInventory()->GetSOC() )
+ {
+ pGameAccountClient = TFInventoryManager()->GetLocalTFInventory()->GetSOC()->GetSingleton<CEconGameAccountClient>();
+ }
+
+ const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( m_pContents->GetMatchGroup() );
+ EMMPenaltyPool ePenaltyPool = pMatchDesc ? pMatchDesc->m_params.m_ePenaltyPool : eMMPenaltyPool_Invalid;
+
+ CRTime timeExpire = CRTime::RTime32TimeCur();
+ int nDuration = -1;
+ bool bBanned = false;
+
+ if ( pGameAccountClient && ePenaltyPool != eMMPenaltyPool_Invalid )
+ {
+ switch ( ePenaltyPool )
+ {
+ case eMMPenaltyPool_Casual:
+ timeExpire = pGameAccountClient->Obj().matchmaking_casual_ban_expiration();
+ nDuration = pGameAccountClient->Obj().matchmaking_casual_ban_last_duration();
+ bBanned = timeExpire > CRTime::RTime32TimeCur();
+ break;
+ case eMMPenaltyPool_Ranked:
+ timeExpire = pGameAccountClient->Obj().matchmaking_ranked_ban_expiration();
+ nDuration = pGameAccountClient->Obj().matchmaking_ranked_ban_last_duration();
+ bBanned = timeExpire > CRTime::RTime32TimeCur();
+ break;
+ default: Assert( false );
+ }
+
+ SetControlVisible( "MatchmakingBanPanel", bBanned );
+
+ if ( bBanned )
+ {
+ CExLabel *pBanLabel = FindControl<CExLabel>( "MatchmakingBanDurationLabel", true );
+
+ if ( pBanLabel )
+ {
+ timeExpire.SetToGMT( false );
+
+ CRTime rtNow = CRTime::RTime32TimeCur();
+
+ int nSecondsRemaining = timeExpire.GetRTime32() - rtNow.GetRTime32();
+
+ if ( nSecondsRemaining >= 0 )
+ {
+
+ const int nDaysForLongBan = 2;
+
+ int nDaysRemaining = nSecondsRemaining / 86400;
+ int nHoursRemaining = nSecondsRemaining / 3600;
+ int nMinutesRemaining = ( nSecondsRemaining % 3600 ) / 60;
+
+ int nDurationDays = nDuration / 86400;
+ int nDurationHours = nDuration / 3600;
+ int nDurationMinutes = ( nDuration % 3600 ) / 60;
+
+ // Want the remainder hours if we're going to display 'days' remaining
+ if ( nDurationDays >= nDaysForLongBan )
+ {
+ nDurationHours = ( nDuration % ( 86400 * nDurationDays ) ) / 3600;
+
+ if ( nDaysRemaining >= nDaysForLongBan )
+ {
+ nHoursRemaining = ( nSecondsRemaining % ( 86400 * nDaysRemaining ) ) / 3600;
+ }
+ }
+
+ wchar_t wszDaysRemaining[16];
+ wchar_t wszHoursRemaining[16];
+ wchar_t wszMinutesRemaining[16];
+ wchar_t wszDurationDays[16];
+ wchar_t wszDurationHours[16];
+ wchar_t wszDurationMinutes[16];
+
+ _snwprintf( wszDaysRemaining, ARRAYSIZE( wszDaysRemaining ), L"%d", nDaysRemaining );
+ _snwprintf( wszHoursRemaining, ARRAYSIZE( wszHoursRemaining ), L"%d", nHoursRemaining );
+ _snwprintf( wszMinutesRemaining, ARRAYSIZE( wszMinutesRemaining ), L"%d", nMinutesRemaining );
+ _snwprintf( wszDurationDays, ARRAYSIZE( wszDurationDays ), L"%d", nDurationDays );
+ _snwprintf( wszDurationHours, ARRAYSIZE( wszDurationHours ), L"%d", nDurationHours );
+ _snwprintf( wszDurationMinutes, ARRAYSIZE( wszDurationMinutes ), L"%d", nDurationMinutes );
+
+ wchar_t wszLocalized[512];
+
+ // Short ban (less than "nDaysForLongBan" days and thus less than that remaining)
+ if ( nDurationDays < nDaysForLongBan )
+ {
+ // Less than an hour ban
+ if ( nDurationHours < 1 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining_Short" ), 2, wszDurationMinutes, wszMinutesRemaining );
+ }
+ else
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining" ), 4, wszDurationHours, wszDurationMinutes, wszHoursRemaining, wszMinutesRemaining );
+ }
+ }
+ // Long ban (at least "nDaysForLongBan" days) but less than that remaining
+ else if ( nDaysRemaining < nDaysForLongBan )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining_Long_Penalty_Short_Duration" ), 4, wszDurationDays, wszDurationHours, wszHoursRemaining, wszMinutesRemaining );
+ }
+ // Long ban and at least that long remaining)
+ else
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "TF_Matchmaking_Ban_Duration_Remaining_Long_Penalty" ), 4, wszDurationDays, wszDurationHours, wszDaysRemaining, wszHoursRemaining );
+ }
+ pBanLabel->SetText( wszLocalized );
+ }
+ else
+ {
+ pBanLabel->SetText( "#TF_Matchmaking_Ban_Duration_Remaining_Shortly" );
+ }
+ }
+ }
+ }
+
+ m_pNextButton->SetEnabled( m_bNextButtonEnabled && !bBanned && !bNoGC );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle cases not handled by our derived classes
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::HandleBackPressed()
+{
+ // We dont know how or why we got here. Failsafe and just leave.
+ if( GTFGCClientSystem()->GetSearchMode() != GetHandledMode() )
+ {
+ Msg( "Lobby handles mode %d, but search mode is %d. Ending matchmaking", (int)( GetHandledMode() ), (int)( GTFGCClientSystem()->GetSearchMode() ) );
+ }
+
+ GTFGCClientSystem()->EndMatchmaking();
+ ShowPanel( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseLobbyContainerFrame::ShouldShowPartyButton() const
+{
+ return !m_pContents->IsPartyActiveGroupBoxVisible() &&
+ GTFGCClientSystem()->GetWizardStep() != TF_Matchmaking_WizardStep_SEARCHING &&
+ GCClientSystem()->BConnectedtoGC() &&
+ BIsPartyLeader();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::OpenOptionsContextMenu()
+{
+ if ( m_pContextMenu )
+ delete m_pContextMenu;
+
+ m_pContextMenu = new Menu( this, "ContextMenu" );
+ MenuBuilder contextMenuBuilder( m_pContextMenu, this );
+ const char *pszContextMenuBorder = "NotificationDefault";
+ const char *pszContextMenuFont = "HudFontMediumSecondary";
+ m_pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ m_pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+ contextMenuBuilder.AddMenuItem( "#TF_LobbyContainer_Ping", new KeyValues( "Context_Ping" ), "ping" );
+ contextMenuBuilder.AddMenuItem( "#TF_LobbyContainer_Help", "show_explanations", "help" );
+
+ m_pContextMenu->SetVisible(true);
+ m_pContextMenu->AddActionSignalTarget(this);
+
+ m_pContextMenu->MakeReadyForUse();
+
+ Panel* pOptionsButton = FindChildByName( "OptionsButton" );
+
+ if ( !pOptionsButton )
+ {
+ // Position to the cursor's position
+ int nX, nY;
+ g_pVGuiInput->GetCursorPosition( nX, nY );
+ m_pContextMenu->SetPos( nX - 1, nY - 1 );
+ }
+ else
+ {
+ int nOptionsX = pOptionsButton->GetXPos();
+ int nX = Min( nOptionsX, GetWide() - m_pContextMenu->GetWide() );
+ int nY = pOptionsButton->GetYPos() + pOptionsButton->GetTall();
+ m_pContextMenu->SetPos( nX - 1, nY - 1 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyContainerFrame::OpenPingOptions()
+{
+ // just create a new one. panel will destroy itself on close.
+ m_hPingPanel = new CTFPingPanel( this, "PingPanel", m_pContents->GetMatchGroup() );
+}
diff --git a/game/client/tf/vgui/tf_lobby_container_frame.h b/game/client/tf/vgui/tf_lobby_container_frame.h
new file mode 100644
index 0000000..a97002e
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame.h
@@ -0,0 +1,88 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_LOBBY_CONTAINER_FRAME_H
+#define TF_LOBBY_CONTAINER_FRAME_H
+
+
+#include "cbase.h"
+#include "vgui_controls/PropertyDialog.h"
+#include "GameEventListener.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+bool BIsPartyLeader();
+bool BIsPartyInUIState();
+CSteamID SteamIDFromDecimalString( const char *pszUint64InDecimal );
+
+class CBaseLobbyPanel;
+
+// This is a big fat kludge so I can use the PropertyPage
+class CBaseLobbyContainerFrame : public vgui::PropertyDialog, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CBaseLobbyContainerFrame, PropertyDialog );
+public:
+ CBaseLobbyContainerFrame( const char *pszPanelName );
+ virtual ~CBaseLobbyContainerFrame();
+
+ //
+ // PropertyDialog overrides
+ //
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void OnKeyCodeTyped(vgui::KeyCode code) OVERRIDE;
+ virtual void OnKeyCodePressed(vgui::KeyCode code) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ //
+ // CGameEventListener overrides
+ //
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+ void StartSearch( void );
+ virtual void ShowPanel(bool bShow);
+ void SetNextButtonEnabled( bool bValue );
+
+ virtual void OnThink() OVERRIDE;
+
+ static void LeaveLobbyPanel( bool bConfirmed, void *pContext )
+ {
+ CBaseLobbyContainerFrame* pCaller = (CBaseLobbyContainerFrame*)pContext;
+ if ( bConfirmed && pCaller )
+ {
+ pCaller->OnCommand( "back" );
+ }
+ }
+
+ MESSAGE_FUNC( OpenPingOptions, "Context_Ping" );
+
+protected:
+
+ bool ShouldShowPartyButton() const;
+ virtual void WriteControls();
+ virtual void HandleBackPressed();
+ void OpenOptionsContextMenu();
+
+ CBaseLobbyPanel *m_pContents;
+ vgui::Button *m_pNextButton;
+ vgui::Button *m_pBackButton;
+
+private:
+
+ virtual const char* GetResFile() const = 0;
+ virtual TF_MatchmakingMode GetHandledMode() const = 0;
+ virtual bool VerifyPartyAuthorization() const = 0;
+
+ vgui::Button *m_pStartPartyButton;
+ vgui::Menu *m_pContextMenu;
+
+ bool m_bNextButtonEnabled;
+
+ vgui::DHANDLE< vgui::Panel > m_hPingPanel;
+};
+
+#endif //TF_LOBBY_CONTAINER_FRAME_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_lobby_container_frame_casual.cpp b/game/client/tf/vgui/tf_lobby_container_frame_casual.cpp
new file mode 100644
index 0000000..c5a4d5f
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame_casual.cpp
@@ -0,0 +1,268 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "vgui_avatarimage.h"
+#include "tf_leaderboardpanel.h"
+#include "tf_lobbypanel_casual.h"
+#include "tf_hud_mainmenuoverride.h"
+
+#include "tf_lobby_container_frame_casual.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern ConVar tf_matchmaking_join_in_progress;
+
+//-----------------------------------------------------------------------------
+// Purpose: Override of the generic messagebox dialog to provide a welcome-to-competitive message
+//-----------------------------------------------------------------------------
+ConVar tf_casual_welcome_hide_forever( "tf_casual_welcome_hide_forever", "0", FCVAR_ARCHIVE | FCVAR_HIDDEN );
+ConVar tf_casual_welcome_hide( "tf_casual_welcome_hide", "0", FCVAR_HIDDEN );
+class CTFCasualWelcomeDialog : public CTFMessageBoxDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFCasualWelcomeDialog, CTFMessageBoxDialog );
+public:
+ CTFCasualWelcomeDialog()
+ : CTFMessageBoxDialog( NULL, ( const char * )NULL, NULL, NULL, NULL )
+ {
+ }
+
+ virtual ~CTFCasualWelcomeDialog() {};
+
+ virtual void OnCommand( const char *command )
+ {
+ if ( FStrEq( "hideforever", command ) )
+ {
+ tf_casual_welcome_hide_forever.SetValue( 1 );
+ return;
+ }
+ else if ( FStrEq( "show_explanations", command ) )
+ {
+ CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) );
+ pMMOverride->GetCasualLobbyPanel()->OnCommand( command );
+ OnCommand( "confirm" );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+ }
+
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel )
+ {
+ CheckButton* pNeverAskAgainCheckBox = FindControl< CheckButton >( "NeverShowAgainCheckBox" );
+ if ( panel == pNeverAskAgainCheckBox )
+ {
+ tf_casual_welcome_hide_forever.SetValue( pNeverAskAgainCheckBox->IsSelected() );
+ }
+ }
+
+ virtual const char *GetResFile() OVERRIDE
+ {
+ // FIXME controller?
+ return "Resource/UI/CasualWelcomeDialog.res";
+ }
+};
+
+//-----------------------------------------------------------------------------
+CLobbyContainerFrame_Casual::CLobbyContainerFrame_Casual()
+ : CBaseLobbyContainerFrame( "LobbyContainerFrame" )
+{
+ // Our internal lobby panel
+ m_pContents = new CLobbyPanel_Casual( this, this );
+ m_pContents->AddActionSignalTarget( this );
+ AddPage( m_pContents, "#TF_Matchmaking_HeaderCasual" );
+ GetPropertySheet()->SetNavToRelay( m_pContents->GetName() );
+ m_pContents->SetVisible( true );
+
+ m_pToolTip = new CMainMenuToolTip( this );
+ vgui::EditablePanel* pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "Tooltip_CasualLobby" );
+ pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
+ pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
+ m_pToolTip->SetEmbeddedPanel( pToolTipEmbeddedPanel );
+ m_pToolTip->SetTooltipDelay( 0 );
+}
+
+//-----------------------------------------------------------------------------
+CLobbyContainerFrame_Casual::~CLobbyContainerFrame_Casual( void )
+{
+}
+
+void CLobbyContainerFrame_Casual::ShowPanel( bool bShow )
+{
+ if ( bShow )
+ {
+ if ( tf_casual_welcome_hide.GetBool() == false
+ && tf_casual_welcome_hide_forever.GetBool() == false
+ && GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_CASUAL )
+ {
+ CTFCasualWelcomeDialog *pDialog = vgui::SETUP_PANEL( new CTFCasualWelcomeDialog() );
+
+ if ( pDialog )
+ {
+ tf_casual_welcome_hide.SetValue( 1 );
+ pDialog->Show();
+ }
+ else
+ {
+ Warning( "Failed to create CasualWelcomeDialog. Outdated HUD?\n" );
+ }
+ }
+
+ // Slam to true for casual
+ tf_matchmaking_join_in_progress.SetValue( true );
+ GTFGCClientSystem()->SetSearchJoinLate( true );
+ }
+
+ BaseClass::ShowPanel( bShow );
+}
+
+void CLobbyContainerFrame_Casual::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "next" ) )
+ {
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_CASUAL:
+ StartSearch();
+ break;
+
+ default:
+ AssertMsg1( false, "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+ return;
+ }
+ else if ( FStrEq( command, "show_explanations" ) )
+ {
+ CExplanationPopup *pPopup = FindControl<CExplanationPopup>( "StartExplanation" );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ return;
+ }
+ else if ( FStrEq( command, "show_maps_details_explanation" ) )
+ {
+ CExplanationPopup *pPopup = FindControl<CExplanationPopup>( "MapSelectionDetailsExplanation" );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ return;
+ }
+ else if ( FStrEq( command, "restore_search_criteria" ) )
+ {
+ if ( GTFGCClientSystem() )
+ {
+ GTFGCClientSystem()->ClearCasualSearchCriteria();
+ GTFGCClientSystem()->LoadCasualSearchCriteria();
+ }
+ return;
+ }
+ else if ( FStrEq( command, "save_search_criteria" ) )
+ {
+ if ( GTFGCClientSystem() )
+ {
+ GTFGCClientSystem()->SaveCasualSearchCriteriaToDisk();
+ }
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+void CLobbyContainerFrame_Casual::WriteControls()
+{
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+
+ const char *pszBackButtonText = "#TF_Matchmaking_Back";
+ const char *pszNextButtonText = NULL;
+
+ if ( GCClientSystem()->BConnectedtoGC() )
+ {
+ if ( BIsPartyLeader() )
+ {
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_CASUAL:
+ pszBackButtonText = "#TF_Matchmaking_Back";
+ pszNextButtonText = "#TF_Matchmaking_StartSearch";
+ break;
+
+ case TF_Matchmaking_WizardStep_SEARCHING:
+ pszBackButtonText = "#TF_Matchmaking_CancelSearch";
+ break;
+
+ case TF_Matchmaking_WizardStep_INVALID:
+ // Still being setup
+ break;
+
+ default:
+ AssertMsg1( false, "Unknown wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+ }
+ else
+ {
+ pszBackButtonText = "#TF_Matchmaking_LeaveParty";
+ m_pNextButton->SetEnabled( false );
+ }
+ }
+
+ m_pBackButton->SetText( pszBackButtonText );
+ m_pNextButton->SetText( pszNextButtonText );
+ m_pNextButton->SetVisible( pszNextButtonText != NULL );
+
+ BaseClass::WriteControls();
+}
+
+bool CLobbyContainerFrame_Casual::VerifyPartyAuthorization() const
+{
+ // For now, there's no additional restrictions for playing casual
+ return true;
+}
+
+void CLobbyContainerFrame_Casual::HandleBackPressed()
+{
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_CASUAL:
+ // !FIXME! Really need to confirm this!
+ GTFGCClientSystem()->EndMatchmaking();
+ // And hide us
+ ShowPanel( false );
+ return;
+
+ case TF_Matchmaking_WizardStep_SEARCHING:
+ switch ( GTFGCClientSystem()->GetSearchMode() )
+ {
+ case TF_Matchmaking_CASUAL:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL );
+ return;
+ }
+ break;
+
+ default:
+ Msg( "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+
+ BaseClass::HandleBackPressed();
+}
+
diff --git a/game/client/tf/vgui/tf_lobby_container_frame_casual.h b/game/client/tf/vgui/tf_lobby_container_frame_casual.h
new file mode 100644
index 0000000..958687e
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame_casual.h
@@ -0,0 +1,82 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_LOBBY_CONTAINER_FRAME_CASUAL_H
+#define TF_LOBBY_CONTAINER_FRAME_CASUAL_H
+
+
+#include "cbase.h"
+//#include "tf_pvelobbypanel.h"
+#include "game/client/iviewport.h"
+#include "tf_shareddefs.h"
+#include "econ/confirm_dialog.h"
+#include "econ/econ_controls.h"
+#include "ienginevgui.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+#include "tf_item_inventory.h"
+#include "econ_ui.h"
+
+#include "vgui_controls/Tooltip.h"
+#include "vgui_controls/PropertyDialog.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/RadioButton.h"
+#include "vgui_controls/SectionedListPanel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "vgui_bitmapimage.h"
+#include "vgui/IInput.h"
+#include <vgui_controls/ImageList.h>
+#include <vgui/IVGui.h>
+#include "GameEventListener.h"
+#include "vgui_avatarimage.h"
+#include <vgui/ISurface.h>
+#include <VGuiMatSurface/IMatSystemSurface.h>
+#include "rtime.h"
+#include "econ_game_account_client.h"
+#include "tf_leaderboardpanel.h"
+#include "tf_mapinfo.h"
+#include "tf_ladder_data.h"
+#include "tf_gamerules.h"
+#include "confirm_dialog.h"
+#include "tf_lobby_container_frame.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+class CBaseLobbyPanel;
+
+// This is a big fat kludge so I can use the PropertyPage
+class CLobbyContainerFrame_Casual : public CBaseLobbyContainerFrame
+{
+ DECLARE_CLASS_SIMPLE( CLobbyContainerFrame_Casual, CBaseLobbyContainerFrame );
+public:
+ CLobbyContainerFrame_Casual();
+ ~CLobbyContainerFrame_Casual();
+
+ //
+ // PropertyDialog overrides
+ //
+ virtual void ShowPanel( bool bShow ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ CMainMenuToolTip *GetTooltipPanel(){ return m_pToolTip; }
+
+protected:
+
+ virtual void WriteControls();
+
+private:
+ virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyContainerFrame_Casual.res"; }
+ virtual TF_MatchmakingMode GetHandledMode() const { return TF_Matchmaking_CASUAL; }
+ virtual bool VerifyPartyAuthorization() const;
+ virtual void HandleBackPressed() OVERRIDE;
+
+
+ CMainMenuToolTip *m_pToolTip;
+};
+
+#endif //TF_LOBBY_CONTAINER_FRAME_CASUAL_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_lobby_container_frame_comp.cpp b/game/client/tf/vgui/tf_lobby_container_frame_comp.cpp
new file mode 100644
index 0000000..838f271
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame_comp.cpp
@@ -0,0 +1,297 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "vgui_avatarimage.h"
+#include "tf_leaderboardpanel.h"
+#include "tf_lobbypanel_comp.h"
+#include "tf_hud_mainmenuoverride.h"
+
+#include "tf_lobby_container_frame_comp.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+// Purpose: Override of the generic messagebox dialog to provide a welcome-to-competitive message
+
+//-----------------------------------------------------------------------------
+ConVar tf_comp_welcome_hide_forever( "tf_comp_welcome_hide_forever", "0", FCVAR_ARCHIVE | FCVAR_HIDDEN );
+ConVar tf_comp_welcome_hide( "tf_comp_welcome_hide", "0", FCVAR_HIDDEN );
+class CTFCompetitiveWelcomeDialog : public CTFMessageBoxDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFCompetitiveWelcomeDialog, CTFMessageBoxDialog );
+public:
+ CTFCompetitiveWelcomeDialog()
+ : CTFMessageBoxDialog( NULL, (const char *)NULL, NULL, NULL, NULL )
+ {}
+
+ virtual ~CTFCompetitiveWelcomeDialog() {};
+
+ virtual void OnCommand( const char *command )
+ {
+ if ( FStrEq( "hideforever", command ) )
+ {
+ tf_comp_welcome_hide_forever.SetValue( 1 );
+ return;
+ }
+ else if ( FStrEq( "show_explanations", command ) )
+ {
+ CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) );
+ pMMOverride->GetCompLobbyPanel()->OnCommand( command );
+ OnCommand( "confirm" );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+ }
+
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel )
+ {
+ CheckButton* pNeverAskAgainCheckBox = FindControl< CheckButton >( "NeverShowAgainCheckBox" );
+ if ( panel == pNeverAskAgainCheckBox )
+ {
+ tf_comp_welcome_hide_forever.SetValue( pNeverAskAgainCheckBox->IsSelected() );
+ }
+ }
+
+ virtual const char *GetResFile() OVERRIDE
+ {
+ // FIXME controller?
+ return "Resource/UI/CompetitiveWelcomeDialog.res";
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+CLobbyContainerFrame_Comp::CLobbyContainerFrame_Comp()
+ : CBaseLobbyContainerFrame( "LobbyContainerFrame" )
+{
+ // Our internal lobby panel
+ m_pContents = new CLobbyPanel_Comp( this, this );
+ m_pContents->AddActionSignalTarget( this );
+ AddPage( m_pContents, "#TF_Matchmaking_HeaderCompetitive" );
+ GetPropertySheet()->SetNavToRelay( m_pContents->GetName() );
+ m_pContents->SetVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+CLobbyContainerFrame_Comp::~CLobbyContainerFrame_Comp( void )
+{
+}
+
+void CLobbyContainerFrame_Comp::ShowPanel( bool bShow )
+{
+ if ( bShow )
+ {
+ if ( tf_comp_welcome_hide.GetBool() == false
+ && tf_comp_welcome_hide_forever.GetBool() == false
+ && GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_LADDER )
+ {
+ CTFCompetitiveWelcomeDialog *pDialog = vgui::SETUP_PANEL( new CTFCompetitiveWelcomeDialog() );
+
+ if ( pDialog )
+ {
+ tf_comp_welcome_hide.SetValue( 1 );
+ pDialog->Show();
+ }
+ else
+ {
+ Warning( "Failed to create CompetitiveWelcomeDialog. Outdated HUD?\n" );
+ }
+ }
+ }
+
+
+
+ BaseClass::ShowPanel( bShow );
+}
+
+void CLobbyContainerFrame_Comp::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "next" ) )
+ {
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_LADDER:
+ StartSearch();
+ break;
+
+ default:
+ AssertMsg1( false, "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+ return;
+ }
+ else if ( FStrEq( command, "show_explanations" ) )
+ {
+ CExplanationPopup *pPopup = FindControl<CExplanationPopup>( "StartExplanation" );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+void CLobbyContainerFrame_Comp::WriteControls()
+{
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+
+ const char *pszBackButtonText = "#TF_Matchmaking_Back";
+ const char *pszNextButtonText = NULL;
+
+ if ( GCClientSystem()->BConnectedtoGC() )
+ {
+ if ( BIsPartyLeader() )
+ {
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_LADDER:
+ pszBackButtonText = "#TF_Matchmaking_Back";
+ pszNextButtonText = "#TF_Matchmaking_StartSearch";
+ break;
+
+ case TF_Matchmaking_WizardStep_SEARCHING:
+ pszBackButtonText = "#TF_Matchmaking_CancelSearch";
+ break;
+
+ case TF_Matchmaking_WizardStep_INVALID:
+ // Still being setup
+ break;
+
+ default:
+ AssertMsg1( false, "Unknown wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+ }
+ else
+ {
+ pszBackButtonText = "#TF_Matchmaking_LeaveParty";
+ m_pNextButton->SetEnabled( false );
+ }
+ }
+
+ m_pBackButton->SetText( pszBackButtonText );
+ m_pNextButton->SetText( pszNextButtonText );
+ m_pNextButton->SetVisible( pszNextButtonText != NULL );
+
+ BaseClass::WriteControls();
+}
+
+//-----------------------------------------------------------------------------
+bool CheckCompetitiveConvars()
+{
+ static ConVarRef mat_dxlevel( "mat_dxlevel");
+ return mat_dxlevel.GetInt() >= 90;
+}
+
+//-----------------------------------------------------------------------------
+bool CLobbyContainerFrame_Comp::VerifyPartyAuthorization() const
+{
+ // Solo
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty == NULL || pParty->GetNumMembers() <= 1 )
+ {
+ if ( !GTFGCClientSystem()->BHasCompetitiveAccess() )
+ {
+ ShowEconRequirementDialog( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", CTFItemSchema::k_rchLadderPassItemDefName );
+ return false;
+ }
+ else if ( !CheckCompetitiveConvars() )
+ {
+ ShowMessageBox( "#TF_Competitive_Convars_CantProceed_Title", "#TF_Competitive_Convars_CantProceed", "#GameUI_OK" );
+ return false;
+ }
+ }
+ // Group
+ else
+ {
+ wchar_t wszLocalized[512];
+ char szLocalized[512];
+ wchar_t wszCharPlayerName[128];
+
+ bool bAnyMembersWithoutAuth = false;
+
+ for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i )
+ {
+ // Need a ticket and two-factor for the beta
+ if ( !pParty->Obj().members( i ).competitive_access() )
+ {
+ bAnyMembersWithoutAuth = true;
+ V_UTF8ToUnicode( steamapicontext->SteamFriends()->GetFriendPersonaName( pParty->GetMember( i ) ), wszCharPlayerName, sizeof( wszCharPlayerName ) );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_MissingPass" ), 1, wszCharPlayerName );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) );
+
+ GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader, szLocalized );
+ }
+ }
+
+ if ( bAnyMembersWithoutAuth )
+ {
+ ShowMessageBox( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", "#GameUI_OK" );
+ return false;
+ }
+ }
+
+ CLobbyPanel_Comp* pCompContents = static_cast< CLobbyPanel_Comp* >( m_pContents );
+ if ( pCompContents )
+ {
+ uint32 unModeType = pCompContents->GetMatchGroup();
+
+ if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last )
+ {
+ GTFGCClientSystem()->SetLadderType( unModeType );
+ }
+ }
+
+ return true;
+}
+
+void CLobbyContainerFrame_Comp::HandleBackPressed()
+{
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_LADDER:
+ // !FIXME! Really need to confirm this!
+ GTFGCClientSystem()->EndMatchmaking();
+ // And hide us
+ ShowPanel( false );
+ return;
+
+ case TF_Matchmaking_WizardStep_SEARCHING:
+ switch ( GTFGCClientSystem()->GetSearchMode() )
+ {
+ case TF_Matchmaking_LADDER:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
+ return;
+ }
+ break;
+
+ default:
+ Msg( "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+
+ // Unhandled case
+ BaseClass::HandleBackPressed();
+}
diff --git a/game/client/tf/vgui/tf_lobby_container_frame_comp.h b/game/client/tf/vgui/tf_lobby_container_frame_comp.h
new file mode 100644
index 0000000..6c06bf0
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame_comp.h
@@ -0,0 +1,77 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_LOBBY_CONTAINER_FRAME_COMP_H
+#define TF_LOBBY_CONTAINER_FRAME_COMP_H
+
+
+#include "cbase.h"
+//#include "tf_pvelobbypanel.h"
+#include "game/client/iviewport.h"
+#include "tf_shareddefs.h"
+#include "econ/confirm_dialog.h"
+#include "econ/econ_controls.h"
+#include "ienginevgui.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+#include "tf_item_inventory.h"
+#include "econ_ui.h"
+
+#include "vgui_controls/Tooltip.h"
+#include "vgui_controls/PropertyDialog.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/RadioButton.h"
+#include "vgui_controls/SectionedListPanel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "vgui_bitmapimage.h"
+#include "vgui/IInput.h"
+#include <vgui_controls/ImageList.h>
+#include <vgui/IVGui.h>
+#include "GameEventListener.h"
+#include "vgui_avatarimage.h"
+#include <vgui/ISurface.h>
+#include <VGuiMatSurface/IMatSystemSurface.h>
+#include "rtime.h"
+#include "econ_game_account_client.h"
+#include "tf_leaderboardpanel.h"
+#include "tf_mapinfo.h"
+#include "tf_ladder_data.h"
+#include "tf_gamerules.h"
+#include "confirm_dialog.h"
+#include "tf_lobby_container_frame.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+class CBaseLobbyPanel;
+
+// This is a big fat kludge so I can use the PropertyPage
+class CLobbyContainerFrame_Comp : public CBaseLobbyContainerFrame
+{
+ DECLARE_CLASS_SIMPLE( CLobbyContainerFrame_Comp, CBaseLobbyContainerFrame );
+public:
+ CLobbyContainerFrame_Comp();
+ ~CLobbyContainerFrame_Comp();
+
+ //
+ // PropertyDialog overrides
+ //
+ virtual void ShowPanel( bool bShow ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+protected:
+
+ virtual void WriteControls();
+
+private:
+ virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyContainerFrame_Comp.res"; }
+ virtual TF_MatchmakingMode GetHandledMode() const { return TF_Matchmaking_LADDER; }
+ virtual bool VerifyPartyAuthorization() const;
+ virtual void HandleBackPressed() OVERRIDE;
+};
+
+#endif //TF_LOBBY_CONTAINER_FRAME_COMP_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_lobby_container_frame_mvm.cpp b/game/client/tf/vgui/tf_lobby_container_frame_mvm.cpp
new file mode 100644
index 0000000..016f5a1
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame_mvm.cpp
@@ -0,0 +1,357 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_party.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/SectionedListPanel.h"
+#include "tf_lobbypanel_mvm.h"
+
+#include "tf_lobby_container_frame_mvm.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+CLobbyContainerFrame_MvM::CLobbyContainerFrame_MvM()
+ : CBaseLobbyContainerFrame( "LobbyContainerFrame" )
+{
+ // Our internal lobby panel
+ m_pContents = new CLobbyPanel_MvM( this, this );
+ m_pContents->MoveToFront();
+ m_pContents->AddActionSignalTarget( this );
+ AddPage( m_pContents, "#TF_Matchmaking_HeaderMvM" );
+ GetPropertySheet()->SetNavToRelay( m_pContents->GetName() );
+ m_pContents->SetVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+CLobbyContainerFrame_MvM::~CLobbyContainerFrame_MvM( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+void CLobbyContainerFrame_MvM::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pStartPartyButton = dynamic_cast<vgui::Button *>(FindChildByName( "StartPartyButton", true )); Assert( m_pStartPartyButton );
+ m_pPlayNowButton = dynamic_cast<vgui::Button *>(FindChildByName( "PlayNowButton", true )); Assert( m_pPlayNowButton );
+ m_pPracticeButton = dynamic_cast<vgui::Button *>(FindChildByName( "PracticeButton", true )); Assert( m_pPracticeButton );
+}
+
+bool CLobbyContainerFrame_MvM::VerifyPartyAuthorization() const
+{
+ // They want to Mann Up. Confirm that everybody in the party has a ticket.
+ // if they are in a party of one, we provide slightly more specific UI.
+ bool bBraggingRights = GTFGCClientSystem()->GetSearchPlayForBraggingRights();
+
+ // Early out. Anyone can play for free
+ if ( !bBraggingRights )
+ return true;
+
+ // Solo
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty == NULL || pParty->GetNumMembers() <= 1 )
+ {
+ if ( bBraggingRights && !GTFGCClientSystem()->BLocalPlayerInventoryHasMvmTicket() )
+ {
+ ShowEconRequirementDialog( "#TF_MvM_RequiresTicket_Title", "#TF_MvM_RequiresTicket", CTFItemSchema::k_rchMvMTicketItemDefName );
+ return false;
+ }
+ }
+ // Group
+ else
+ {
+ wchar_t wszLocalized[512];
+ char szLocalized[512];
+ wchar_t wszCharPlayerName[128];
+
+ bool bAnyMembersWithoutAuth = false;
+
+ if ( bBraggingRights )
+ {
+ for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i )
+ {
+ if ( !pParty->Obj().members( i ).owns_ticket() )
+ {
+ bAnyMembersWithoutAuth = true;
+
+ V_UTF8ToUnicode( steamapicontext->SteamFriends()->GetFriendPersonaName( pParty->GetMember( i ) ), wszCharPlayerName, sizeof( wszCharPlayerName ) );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_MissingTicket" ), 1, wszCharPlayerName );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) );
+
+ GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader, szLocalized );
+ }
+ }
+ }
+
+ if ( bAnyMembersWithoutAuth )
+ {
+ if ( bBraggingRights )
+ {
+ ShowMessageBox( "#TF_MvM_RequiresTicket_Title", "#TF_MvM_RequiresTicketParty", "#GameUI_OK" );
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void CLobbyContainerFrame_MvM::HandleBackPressed()
+{
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS:
+ // !FIXME! Rreally need to confirm this!
+ GTFGCClientSystem()->EndMatchmaking();
+ // And hide us
+ ShowPanel( false );
+ return;
+
+#ifdef USE_MVM_TOUR
+ case TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS );
+ return;
+#endif // USE_MVM_TOUR
+
+ case TF_Matchmaking_WizardStep_MVM_CHALLENGE:
+#ifdef USE_MVM_TOUR
+ if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() )
+ {
+ TF_Matchmaking_WizardStep step = TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY;
+ GTFGCClientSystem()->RequestSelectWizardStep( step );
+ }
+ else
+ {
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS );
+ }
+#else // new mm
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS );
+#endif // USE_MVM_TOUR
+ return;
+
+ case TF_Matchmaking_WizardStep_SEARCHING:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+ return;
+
+ default:
+ Msg( "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+
+ // Unhandled case
+ BaseClass::HandleBackPressed();
+}
+
+//-----------------------------------------------------------------------------
+void CLobbyContainerFrame_MvM::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "learn_more" ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.teamfortress.com/mvm/" );
+ }
+ return;
+ }
+ else if ( FStrEq( command, "mannup" ) )
+ {
+ GTFGCClientSystem()->SetSearchPlayForBraggingRights( true );
+#ifdef USE_MVM_TOUR
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY );
+#else // new mm
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+#endif // USE_MVM_TOUR
+ return;
+ }
+ else if ( FStrEq( command, "practice" ) )
+ {
+ GTFGCClientSystem()->SetSearchPlayForBraggingRights( false );
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+ return;
+ }
+ else if ( FStrEq( command, "next" ) )
+ {
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS:
+#ifdef USE_MVM_TOUR
+ if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() )
+ {
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY );
+ }
+ else
+ {
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+ }
+ break;
+
+ case TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY:
+#endif // USE_MVM_TOUR
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+ break;
+
+ case TF_Matchmaking_WizardStep_MVM_CHALLENGE:
+ case TF_Matchmaking_WizardStep_LADDER:
+ StartSearch();
+ break;
+
+ default:
+ AssertMsg1( false, "Unexpected wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyContainerFrame_MvM::OnKeyCodePressed(vgui::KeyCode code)
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if ( nButtonCode == KEY_XBUTTON_Y )
+ {
+ static_cast< CLobbyPanel_MvM* >( m_pContents )->ToggleSquadSurplusCheckButton();
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+
+//-----------------------------------------------------------------------------
+void CLobbyContainerFrame_MvM::WriteControls()
+{
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+ const char *pszBackButtonText = "#TF_Matchmaking_Back";
+ const char *pszNextButtonText = NULL;
+
+ CMvMMissionSet challenges;
+ GTFGCClientSystem()->GetSearchChallenges( challenges );
+
+ bool bShowPlayNowButtons = false;
+
+ if ( GCClientSystem()->BConnectedtoGC() )
+ {
+ if ( BIsPartyLeader() )
+ {
+ switch ( GTFGCClientSystem()->GetWizardStep() )
+ {
+ case TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS:
+ {
+ if ( !m_pStartPartyButton->IsVisible() )
+ {
+ pszBackButtonText = "#TF_Matchmaking_LeaveParty";
+ }
+ else
+ {
+ pszBackButtonText = "#TF_Matchmaking_Back";
+ }
+
+ bShowPlayNowButtons = BIsPartyLeader();
+ break;
+ }
+
+ case TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY:
+#ifdef USE_MVM_TOUR
+ pszBackButtonText = "#TF_Matchmaking_Back";
+ pszNextButtonText = "#TF_MvM_SelectChallenge";
+
+ SetNextButtonEnabled( GTFGCClientSystem()->GetSearchMannUpTourIndex() >= 0 );
+#else // new mm
+ AssertMsg( 0, "This is legacy code. We don't have concept of tour anymore." );
+#endif // USE_MVM_TOUR
+ break;
+
+ case TF_Matchmaking_WizardStep_MVM_CHALLENGE:
+ pszBackButtonText = "#TF_Matchmaking_Back";
+
+ pszNextButtonText = "#TF_Matchmaking_StartSearch";
+ SetNextButtonEnabled( !challenges.IsEmpty() );
+
+ break;
+
+ case TF_Matchmaking_WizardStep_SEARCHING:
+ pszBackButtonText = "#TF_Matchmaking_CancelSearch";
+ break;
+
+ case TF_Matchmaking_WizardStep_INVALID:
+ // Still being setup
+ break;
+
+ default:
+ AssertMsg1( false, "Unknown wizard step %d", (int)GTFGCClientSystem()->GetWizardStep() );
+ break;
+ }
+ }
+ else
+ {
+ pszBackButtonText = "#TF_Matchmaking_LeaveParty";
+ m_pNextButton->SetEnabled( false );
+ }
+ }
+
+ m_pPlayNowButton->SetVisible( bShowPlayNowButtons );
+ m_pPracticeButton->SetVisible( bShowPlayNowButtons );
+ SetControlVisible( "LearnMoreButton", GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS );
+
+ // Set appropriate page title
+ switch ( GTFGCClientSystem()->GetSearchMode() )
+ {
+ case TF_Matchmaking_MVM:
+ if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() ||
+ GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS )
+ {
+ GetPropertySheet()->SetTabTitle( 0, "#TF_MvM_HeaderCoop" );
+ }
+ else
+ {
+ GetPropertySheet()->SetTabTitle( 0, "#TF_MvM_HeaderPractice" );
+ }
+ break;
+
+ default:
+ AssertMsg1( false, "Invalid search mode %d", GTFGCClientSystem()->GetSearchMode() );
+ break;
+ }
+
+ // Check if we already have a party, then make sure and show it
+ if ( m_pStartPartyButton->IsVisible() && m_pContents->NumPlayersInParty() > 1 )
+ {
+ m_pContents->SetControlVisible( "PartyActiveGroupBox", true );
+ }
+
+ SetControlVisible( "PlayWithFriendsExplanation", ShouldShowPartyButton() );
+
+
+ static_cast< CLobbyPanel_MvM* >( m_pContents )->SetMannUpTicketCount( GTFGCClientSystem()->GetLocalPlayerInventoryMvmTicketCount() );
+ static_cast< CLobbyPanel_MvM* >( m_pContents )->SetSquadSurplusCount( GTFGCClientSystem()->GetLocalPlayerInventorySquadSurplusVoucherCount() );
+
+ m_pBackButton->SetText( pszBackButtonText );
+ if ( pszNextButtonText )
+ {
+ m_pNextButton->SetText( pszNextButtonText );
+ m_pNextButton->SetVisible( true );
+ }
+ else
+ {
+ m_pNextButton->SetVisible( false );
+ }
+
+ BaseClass::WriteControls();
+}
+
diff --git a/game/client/tf/vgui/tf_lobby_container_frame_mvm.h b/game/client/tf/vgui/tf_lobby_container_frame_mvm.h
new file mode 100644
index 0000000..0ed0548
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobby_container_frame_mvm.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_LOBBY_CONTAINER_FRAME_MVM_H
+#define TF_LOBBY_CONTAINER_FRAME_MVM_H
+
+
+#include "cbase.h"
+//#include "tf_pvelobbypanel.h"
+#include "tf_lobby_container_frame.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+class CBaseLobbyPanel;
+
+// This is a big fat kludge so I can use the PropertyPage
+class CLobbyContainerFrame_MvM : public CBaseLobbyContainerFrame
+{
+ DECLARE_CLASS_SIMPLE( CLobbyContainerFrame_MvM, CBaseLobbyContainerFrame );
+public:
+ CLobbyContainerFrame_MvM();
+ ~CLobbyContainerFrame_MvM();
+
+ //
+ // PropertyDialog overrides
+ //
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void OnKeyCodePressed(vgui::KeyCode code) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+private:
+
+ virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyContainerFrame_MvM.res"; }
+ virtual TF_MatchmakingMode GetHandledMode() const { return TF_Matchmaking_MVM; }
+ virtual bool VerifyPartyAuthorization() const OVERRIDE;
+ virtual void WriteControls() OVERRIDE;
+ virtual void HandleBackPressed() OVERRIDE;
+
+ vgui::Button *m_pStartPartyButton;
+ vgui::Button *m_pPlayNowButton;
+ vgui::Button *m_pPracticeButton;
+};
+
+#endif //TF_LOBBY_CONTAINER_FRAME_MVM_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_lobbypanel.cpp b/game/client/tf/vgui/tf_lobbypanel.cpp
new file mode 100644
index 0000000..8f8a182
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel.cpp
@@ -0,0 +1,1107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "tf_party.h"
+#include "tf_item_inventory.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/SectionedListPanel.h"
+#include "vgui/IInput.h"
+#include <vgui_controls/ImageList.h>
+#include "vgui_avatarimage.h"
+#include "tf_ladder_data.h"
+#include "vgui_controls/Menu.h"
+#include "tf_match_description.h"
+#include "tf_badge_panel.h"
+#include "tf_controls.h"
+
+#include "tf_lobbypanel.h"
+#include "tf_lobby_container_frame.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+ConVar tf_matchmaking_join_in_progress( "tf_matchmaking_join_in_progress", "0", FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Saved preference for if the player wants to join games in progress." );
+
+const int k_iPopIndex_Any = -1000;
+const int k_iPopIndex_OnlyNotYetCompleted = -1001;
+const int k_iPopIndex_AnyNormal = -1002;
+const int k_iPopIndex_AnyIntermediate = -1003;
+const int k_iPopIndex_AnyAdvanced = -1004;
+const int k_iPopIndex_AnyExpert = -1005;
+const int k_iPopIndex_AnyHaunted = -1006;
+
+static void GetMvmChallengeSet( int idxChallenge, CMvMMissionSet &result )
+{
+ result.Clear();
+
+ if ( idxChallenge >= 0 )
+ {
+ result.SetMissionBySchemaIndex( idxChallenge, true );
+ return;
+ }
+
+ bool bMannUP = GTFGCClientSystem()->GetSearchPlayForBraggingRights();
+#ifdef USE_MVM_TOUR
+ int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex();
+ Assert( bMannUP || idxTour < 0 );
+#endif // USE_MVM_TOUR
+
+#ifdef USE_MVM_TOUR
+ uint32 nNotCompletedChallenges = ~0U;
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty )
+ {
+ for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i )
+ {
+ nNotCompletedChallenges &= ~pParty->Obj().members( i ).completed_missions();
+ }
+ }
+ else
+ {
+ if ( idxTour >= 0 )
+ {
+ uint32 nTours = 0, nCompletedChallenge = 0;
+ GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge );
+
+ nNotCompletedChallenges = ~nCompletedChallenge;
+ }
+ }
+#endif // USE_MVM_TOUR
+
+ for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
+ {
+ const MvMMission_t &chal = GetItemSchema()->GetMvmMissions()[ i ];
+
+ // Cannot select non-MannUp missions in mann up mode
+#ifdef USE_MVM_TOUR
+ int iBadgeSlot = (idxTour < 0) ? -1 : GetItemSchema()->GetMvmMissionBadgeSlotForTour( idxTour, i );
+ if ( bMannUP && iBadgeSlot < 0 )
+ continue;
+#else // new mm
+ bool bIsChallengeInMannUp = chal.m_unMannUpPoints > 0;
+ if ( bMannUP && !bIsChallengeInMannUp )
+ continue;
+#endif // USE_MVM_TOUR
+
+ // Does this challenge fit the search criteria?
+ bool bSelect = false;
+ switch ( idxChallenge )
+ {
+ case k_iPopIndex_Any:
+ bSelect = true;
+ break;
+ case k_iPopIndex_OnlyNotYetCompleted:
+#ifdef USE_MVM_TOUR
+ if ( iBadgeSlot >= 0 )
+ {
+ int iChallengeBit = ( 1 << iBadgeSlot );
+ if ( nNotCompletedChallenges & iChallengeBit )
+ {
+ bSelect = true;
+ }
+ }
+#endif // USE_MVM_TOUR
+ break;
+
+ case k_iPopIndex_AnyNormal:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Normal );
+ break;
+
+ case k_iPopIndex_AnyIntermediate:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Intermediate );
+ break;
+
+ case k_iPopIndex_AnyAdvanced:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Advanced );
+ break;
+
+ case k_iPopIndex_AnyExpert:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Expert );
+ break;
+
+ case k_iPopIndex_AnyHaunted:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Haunted );
+ break;
+
+ default:
+ Assert( false );
+ }
+ result.SetMissionBySchemaIndex( i, bSelect );
+ }
+}
+
+#ifdef ENABLE_GC_MATCHMAKING
+
+Color s_colorBannedPlayerListItem( 250, 50, 45, 255 );
+Color s_colorPlayerListItem( 255, 255, 255, 255 );
+Color s_colorChatRemovedFromQueue( 200, 10, 10, 255 );
+Color s_colorChatAddedToQueue( 10, 200, 10, 255 );
+Color s_colorChatPlayerJoinedParty( 255, 255, 255, 255 );
+Color s_colorChatPlayerJoinedPartyName( 200, 200, 10, 255 );
+Color s_colorChatPlayerLeftParty( 255, 255, 255, 255 );
+Color s_colorChatPlayerLeftPartyName( 200, 200, 10, 255 );
+Color s_colorChatPlayerChatName( 200, 200, 10, 255 );
+Color s_colorChatPlayerChatText( 180, 180, 180, 255 );
+Color s_colorChatDefault( 180, 180, 180, 255 );
+Color s_colorChallengeForegroundEnabled( 255, 255, 255, 255 );
+Color s_colorChallengeForegroundHaunted( 135, 79, 173, 255 );
+Color s_colorChallengeForegroundDisabled( 100, 100, 100, 128 );
+Color s_colorChallengeHeader( 250, 114, 45, 255 );
+
+static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID )
+{
+ const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
+ V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes );
+}
+
+CBaseLobbyPanel::CBaseLobbyPanel( vgui::Panel *pParent, CBaseLobbyContainerFrame* pContainer )
+ : vgui::PropertySheet( pParent, "LobbyPanel" ), m_sPersonaStateChangedCallback( this, &CBaseLobbyPanel::OnPersonaStateChanged )
+ , m_pContainer( pContainer )
+{
+ //ListenForGameEvent( "lobby_updated" );
+ ListenForGameEvent( "party_updated" );
+ ListenForGameEvent( "mm_lobby_chat" );
+ ListenForGameEvent( "mm_lobby_member_join" );
+ ListenForGameEvent( "mm_lobby_member_leave" );
+
+ m_iWritingPanel = 0;
+
+ m_pSearchActiveGroupBox = NULL;
+ m_pSearchActiveTitleLabel = NULL;
+ m_pSearchActivePenaltyLabel = NULL;
+ m_pPartyHasLowPriority = NULL;
+
+ m_pJoinLateCheckButton = NULL;
+ m_pJoinLateValueLabel = NULL;
+
+ m_pInviteButton = NULL;
+ m_pChatLog = NULL;
+ //m_nFirstMapShown = -1;
+ m_pImageList = NULL;
+
+ m_iImageIsBanned = -1;
+ m_iImageRadioButtonYes = -1;
+ m_iImageRadioButtonNo = -1;
+ m_iImageCheckBoxDisabled = -1;
+ m_iImageCheckBoxYes = -1;
+ m_iImageCheckBoxNo = -1;
+ m_iImageCheckBoxMixed = -1;
+ m_iImageNew = -1;
+ m_iImageNo = -1;
+
+ m_fontPlayerListItem = 0;
+
+ // Party
+ m_pPartyActiveGroupBox = new vgui::EditablePanel( this, "PartyActiveGroupBox" );
+ vgui::EditablePanel *pPartyGroupPanel = new vgui::EditablePanel( m_pPartyActiveGroupBox, "PartyGroupBox" );
+ m_pChatPlayerList = new vgui::SectionedListPanel( pPartyGroupPanel, "PartyPlayerList" );
+ m_pChatTextEntry = new ChatTextEntry( m_pPartyActiveGroupBox, "ChatTextEntry" ); Assert( m_pChatTextEntry );
+ m_pChatLog = new ChatLog( m_pPartyActiveGroupBox, "ChatLog" ); Assert( m_pChatLog );
+
+ m_mapAvatarsToImageList.SetLessFunc( DefLessFunc(int) );
+
+ m_eCurrentPartyState = CSOTFParty_State_UI;
+
+ m_flRefreshPlayerListTime = -1.f;
+
+ m_pToolTip = new CMainMenuToolTip( this );
+ vgui::EditablePanel* pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" );
+ pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
+ pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
+ m_pToolTip->SetEmbeddedPanel( pToolTipEmbeddedPanel );
+ m_pToolTip->SetTooltipDelay( 0 );
+}
+
+CBaseLobbyPanel::~CBaseLobbyPanel()
+{
+ delete m_pImageList;
+ m_pImageList = NULL;
+}
+
+void CBaseLobbyPanel::OnClickedOnPlayer()
+{
+ int iSelected = m_pChatPlayerList->GetSelectedItem();
+ //m_pChatPlayerList->ClearSelection();
+ if ( iSelected < 0 )
+ return;
+ CSteamID steamID = SteamIDFromDecimalString( m_pChatPlayerList->GetItemData( iSelected )->GetString( "steamid", "" ) );
+ if ( !steamID.IsValid() )
+ return;
+
+ vgui::Menu *menu = new vgui::Menu(this, "ContextMenu");
+
+ int x, y;
+ vgui::input()->GetCursorPos(x, y);
+ menu->SetPos(x, y);
+
+ wchar_t wszLocalized[512];
+ char szLocalized[512];
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_ScoreBoard_Context_Trade" ), 0 );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) );
+
+ menu->AddMenuItem( szLocalized, new KeyValues( "TradeWithUser", "steamid", CFmtStr( "%llu", steamID.ConvertToUint64() ).Access() ), this );
+
+ menu->SetVisible(true);
+}
+
+void CBaseLobbyPanel::SetMatchmakingModeBackground()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // Get the background panel.
+ vgui::ImagePanel* pModeBackgroundImage = FindControl< vgui::ImagePanel >( "ModeBackgroundImage", true );
+ if ( !pModeBackgroundImage )
+ return;
+
+ const char* pszImageName = NULL;
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetMatchGroup() );
+ if ( pMatchDesc && pMatchDesc->m_pProgressionDesc )
+ {
+ // Get the level of the local player, and pull the background image out of the level
+ const LevelInfo_t& level = pMatchDesc->m_pProgressionDesc->YieldingGetLevelForSteamID( steamapicontext->SteamUser()->GetSteamID() );
+ pszImageName = level.m_pszLobbyBackgroundImage;
+ }
+
+ // Set the image name, if we got one, into the panel
+ if ( pszImageName )
+ {
+ pModeBackgroundImage->SetImage( pszImageName );
+ }
+
+ // Only show if we got one (MvM doesn't have any)
+ pModeBackgroundImage->SetVisible( pszImageName != NULL );
+}
+
+//-----------------------------------------------------------------------------
+void CBaseLobbyPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( !IsVisible() || !m_pContainer->IsVisible() )
+ return;
+
+ const char *pszEventName = event->GetName();
+ if ( !Q_stricmp( pszEventName, "party_updated" ) )
+ {
+
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty )
+ {
+ if ( m_eCurrentPartyState != pParty->GetState() )
+ {
+ wchar_t wszLocalized[512];
+
+ switch ( pParty->GetState() )
+ {
+ case CSOTFParty_State_UI:
+ m_pChatLog->InsertColorChange( s_colorChatRemovedFromQueue );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_RemovedFromQueue" ), 0 );
+ m_pChatLog->InsertString( wszLocalized );
+ break;
+
+ case CSOTFParty_State_FINDING_MATCH:
+ m_pChatLog->InsertColorChange( s_colorChatAddedToQueue );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_AddedToQueue" ), 0 );
+ m_pChatLog->InsertString( wszLocalized );
+ break;
+
+ default:
+ Assert( false );
+ case CSOTFParty_State_IN_MATCH:
+ break;
+ }
+ m_eCurrentPartyState = pParty->GetState();
+ }
+ }
+ else
+ {
+ m_eCurrentPartyState = CSOTFParty_State_UI;
+ }
+
+ UpdatePlayerList();
+ WriteGameSettingsControls();
+ return;
+ }
+
+ if ( !Q_stricmp( pszEventName, "mm_lobby_chat" ) )
+ {
+ CSteamID steamID = SteamIDFromDecimalString( event->GetString( "steamid", "0" ) );
+
+ const char *pszText = event->GetString( "text", "" );
+ int l = V_strlen( pszText );
+ if ( l > 0 )
+ {
+ int nBufSize = l * sizeof(wchar_t) + 4;
+ wchar_t *wText = (wchar_t *)stackalloc( nBufSize );
+ V_UTF8ToUnicode( pszText, wText, nBufSize );
+ switch ( event->GetInt( "type", CTFGCClientSystem::k_eLobbyMsg_UserChat ) )
+ {
+ default:
+ Assert( !"Unknown chat message type" );
+ case CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader:
+ m_pChatLog->InsertColorChange( s_colorChatDefault );
+ m_pChatLog->InsertString( wText );
+ m_pChatLog->InsertString("\n");
+ break;
+
+ case CTFGCClientSystem::k_eLobbyMsg_UserChat:
+ {
+
+ wchar_t wCharPlayerName[ 128 ];
+ GetPlayerNameForSteamID( wCharPlayerName, sizeof(wCharPlayerName), steamID );
+ m_pChatLog->InsertColorChange( s_colorChatPlayerChatName );
+ m_pChatLog->InsertString( wCharPlayerName );
+ m_pChatLog->InsertString( ": " );
+ m_pChatLog->InsertColorChange( s_colorChatPlayerChatText );
+ m_pChatLog->InsertString( wText );
+ m_pChatLog->InsertString("\n");
+ } break;
+ }
+ }
+
+ return;
+ }
+
+ if ( !Q_stricmp( pszEventName, "mm_lobby_member_join" ) )
+ {
+ CSteamID steamID = SteamIDFromDecimalString( event->GetString( "steamid", "0" ) );
+
+ bool bSolo = false;
+ if ( steamID == steamapicontext->SteamUser()->GetSteamID() )
+ {
+ m_pChatLog->SetText("");
+ bSolo = ( event->GetInt( "solo", 0 ) != 0 );
+ }
+
+ wchar_t wszLocalized[512];
+
+ // An empty lobby by ourselves?
+ if ( bSolo )
+ {
+ m_pChatLog->InsertColorChange( s_colorChatDefault );
+
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_StartSearchChat" ), 0 );
+ m_pChatLog->InsertString( wszLocalized );
+
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_InviteFriendsChat" ), 0 );
+ m_pChatLog->InsertString( wszLocalized );
+ }
+ else
+ {
+ wchar_t wCharPlayerName[128];
+ m_pChatLog->InsertColorChange( s_colorChatPlayerJoinedPartyName );
+ GetPlayerNameForSteamID( wCharPlayerName, sizeof( wCharPlayerName ), steamID );
+ m_pChatLog->InsertColorChange( s_colorChatPlayerJoinedParty );
+
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_PlayerJoinedPartyChat" ), 1, wCharPlayerName );
+ m_pChatLog->InsertString( wszLocalized );
+ }
+
+ UpdatePlayerList();
+
+ return;
+ }
+
+ if ( !Q_stricmp( pszEventName, "mm_lobby_member_leave" ) )
+ {
+ CSteamID steamID = SteamIDFromDecimalString( event->GetString( "steamid", "0" ) );
+ wchar_t wCharPlayerName[ 128 ];
+ GetPlayerNameForSteamID( wCharPlayerName, sizeof(wCharPlayerName), steamID );
+ m_pChatLog->InsertColorChange( s_colorChatPlayerLeftParty );
+
+ wchar_t wszLocalized[512];
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_PlayerLeftPartyChat" ), 1, wCharPlayerName );
+ m_pChatLog->InsertString( wszLocalized );
+
+ UpdatePlayerList();
+ WriteGameSettingsControls();
+
+ return;
+ }
+
+ Assert( false );
+}
+
+void CBaseLobbyPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "invite" ) )
+ {
+ GTFGCClientSystem()->RequestActivateInvite();
+ }
+ else if ( FStrEq( command, "open_charinfo" ) )
+ {
+ engine->ClientCmd_Unrestricted( "open_econui_backpack" );
+ }
+ else
+ {
+ // What other commands are there?
+ Assert( false );
+ }
+}
+
+
+bool CBaseLobbyPanel::IsAnyoneBanned( RTime32 &rtimeExpire ) const
+{
+ bool bBanned = false;
+ RTime32 rtimeHighest = 0;
+
+ // This only matters if we're searching for a mannup or ladder game
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty && ( !pParty->GetSearchPlayForBraggingRights() && !IsLadderGroup( pParty->GetMatchGroup() ) ) )
+ {
+ return false;
+ }
+
+ for( int i=0; i<m_vecPlayers.Count(); ++i )
+ {
+ if ( m_vecPlayers[i].m_bIsBanned )
+ {
+ bBanned = true;
+
+ if ( m_vecPlayers[i].m_rtimeBanExpire > rtimeHighest )
+ {
+ rtimeHighest = m_vecPlayers[i].m_rtimeBanExpire;
+ }
+ }
+ }
+
+ rtimeExpire = rtimeHighest;
+ return bBanned;
+}
+
+const CBaseLobbyPanel::LobbyPlayerInfo* CBaseLobbyPanel::GetLobbyPlayerInfo( CSteamID &steamID ) const
+{
+ for ( int i = 0; i < m_vecPlayers.Count(); ++i )
+ {
+ if ( m_vecPlayers[i].m_steamID == steamID )
+ {
+ return &m_vecPlayers[i];
+ }
+ }
+
+ Assert( false );
+ return NULL;
+}
+
+bool CBaseLobbyPanel::IsAnyoneLowPriority( RTime32 &rtimeExpire ) const
+{
+ bool bLowPriority = false;
+ RTime32 rtimeHighest = 0;
+
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty && !pParty->GetSearchPlayForBraggingRights() && !IsLadderGroup( pParty->GetMatchGroup() ) )
+ return false;
+
+ for ( int i = 0; i < m_vecPlayers.Count(); ++i )
+ {
+ if ( m_vecPlayers[i].m_bIsLowPriority )
+ {
+ bLowPriority = true;
+
+ if ( m_vecPlayers[i].m_rtimeLowPriorityExpire > rtimeHighest )
+ {
+ rtimeHighest = m_vecPlayers[i].m_rtimeLowPriorityExpire;
+ }
+ }
+ }
+
+ rtimeExpire = rtimeHighest;
+ return bLowPriority;
+}
+
+void CBaseLobbyPanel::UpdateControls()
+{
+ WriteGameSettingsControls();
+ WriteStatusControls();
+ UpdatePlayerList();
+}
+
+void CBaseLobbyPanel::OnCheckButtonChecked( vgui::Panel *panel )
+{
+ if ( m_iWritingPanel > 0 )
+ return;
+ if ( panel == m_pJoinLateCheckButton )
+ {
+ if ( BIsPartyLeader() && GCClientSystem()->BConnectedtoGC() )
+ {
+ tf_matchmaking_join_in_progress.SetValue( m_pJoinLateCheckButton->IsSelected() ? 1 : 0 );
+ GTFGCClientSystem()->SetSearchJoinLate( m_pJoinLateCheckButton->IsSelected() );
+ }
+ else
+ {
+ WriteGameSettingsControls();
+ }
+ }
+}
+
+void CBaseLobbyPanel::OnTradeWithUser( KeyValues* params )
+{
+ CSteamID steamID = SteamIDFromDecimalString( params->GetString( "steamid", "" ) );
+ if ( !steamID.IsValid() )
+ return;
+ steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "jointrade", steamID );
+}
+
+void CBaseLobbyPanel::OnItemLeftClick( vgui::Panel* panel )
+{
+ if ( m_iWritingPanel > 0 )
+ return;
+ m_pChatTextEntry->RequestFocus();
+ if ( panel == m_pChatPlayerList )
+ {
+ OnClickedOnPlayer();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLobbyPanel::WriteStatusControls()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ enum EDisabledState
+ {
+ DISABLED_NONE,
+ DISABLED_NO_GC,
+ DISABLED_MATCH_IN_PROGRESS
+ };
+
+ EDisabledState eDisabled = DISABLED_NONE;
+
+ if ( GTFGCClientSystem()->BHaveLiveMatch() )
+ {
+ eDisabled = DISABLED_MATCH_IN_PROGRESS;
+ }
+
+ if ( !GCClientSystem()->BConnectedtoGC() || ( GTFGCClientSystem()->GetParty() && GTFGCClientSystem()->GetParty()->BOffline() ) )
+ {
+ eDisabled = DISABLED_NO_GC;
+ }
+
+ SetControlVisible( "NoGCGroupBox", eDisabled == DISABLED_NO_GC, true );
+ SetControlVisible( "MatchInProgressGroupBox", eDisabled == DISABLED_MATCH_IN_PROGRESS, true );
+
+ if ( GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_SEARCHING )
+ {
+ m_pSearchActiveGroupBox->SetVisible( true );
+
+ const CMsgMatchmakingProgress &progress = GTFGCClientSystem()->m_msgMatchmakingProgress;
+ wchar_t wszCount[32];
+ CUtlVector<vgui::Label *> vecNearbyFields;
+ vgui::Label *pNearbyColumnHead = dynamic_cast<vgui::Label *>( FindChildByName( "NearbyColumnHead", true ) );
+ Assert( pNearbyColumnHead );
+ vecNearbyFields.AddToTail( pNearbyColumnHead );
+
+ #define DO_FIELD( protobufname, labelname, bNearby ) \
+ vgui::Label *p##labelname = dynamic_cast<vgui::Label *>( FindChildByName( #labelname, true ) ); \
+ Assert( p##labelname ); \
+ if ( p##labelname ) \
+ { \
+ if ( progress.has_##protobufname() ) \
+ { \
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", progress.protobufname() ); \
+ p##labelname->SetText( wszCount ); \
+ if ( bNearby ) bHasAnyNearbyData = true; \
+ } \
+ else \
+ { \
+ p##labelname->SetText( "#TF_Matchmaking_NoData" ); \
+ } \
+ if ( bNearby ) vecNearbyFields.AddToTail( p##labelname ); \
+ }
+
+ bool bHasAnyNearbyData = false;
+ DO_FIELD( matching_worldwide_searching_players, PlayersSearchingMatchingWorldwideValue, false )
+ DO_FIELD( matching_near_you_searching_players, PlayersSearchingMatchingNearbyValue, true )
+ DO_FIELD( matching_worldwide_active_players, PlayersInGameMatchingWorldwideValue, false )
+ DO_FIELD( matching_near_you_active_players, PlayersInGameMatchingNearbyValue, true )
+ DO_FIELD( matching_worldwide_empty_gameservers, EmptyGameserversMatchingWorldwideValue, false )
+ DO_FIELD( matching_near_you_empty_gameservers, EmptyGameserversMatchingNearbyValue, true )
+ DO_FIELD( total_worldwide_searching_players, PlayersSearchingTotalWorldwideValue, false )
+ DO_FIELD( total_near_you_searching_players, PlayersSearchingTotalNearbyValue, true )
+ DO_FIELD( total_worldwide_active_players, PlayersInGameTotalWorldwideValue, false )
+ DO_FIELD( total_near_you_active_players, PlayersInGameTotalNearbyValue, true )
+
+ FOR_EACH_VEC( vecNearbyFields, i )
+ {
+ vecNearbyFields[i]->SetVisible( bHasAnyNearbyData );
+ }
+
+ // Show the low priority message? This only really applies to ladder games for now.
+ RTime32 rtimeExpire = 0;
+ bool bShowTimer = IsAnyoneLowPriority( rtimeExpire );
+ if ( bShowTimer )
+ {
+ CRTime timeExpire( rtimeExpire );
+ timeExpire.SetToGMT( false );
+ char time_buf[k_RTimeRenderBufferSize];
+ if ( m_pPartyHasLowPriority )
+ {
+ m_pPartyHasLowPriority->SetDialogVariable( "penaltytimer", CFmtStr( "Expires: %s", ( ( rtimeExpire > 0 ) ? timeExpire.Render( time_buf ) : "" ) ) );
+ }
+ if ( m_pSearchActivePenaltyLabel )
+ {
+ m_pSearchActivePenaltyLabel->SetText( "#TF_Matchmaking_PartyLowPriority" );
+ }
+ }
+ if ( m_pPartyHasLowPriority )
+ {
+ m_pPartyHasLowPriority->SetVisible( bShowTimer );
+ }
+ if ( m_pSearchActivePenaltyLabel )
+ {
+ m_pSearchActivePenaltyLabel->SetVisible( bShowTimer );
+ }
+
+ // HOLY CHEESEBALL BUSY INDICATOR
+ const wchar_t *pwszEllipses = &L"....."[ 4 - ( (unsigned)Plat_FloatTime() % 5U ) ];
+ wchar_t wszLocalized[512];
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Searching" ), 1, pwszEllipses );
+ if ( m_pSearchActiveTitleLabel )
+ {
+ m_pSearchActiveTitleLabel->SetText( wszLocalized );
+ }
+ }
+ else
+ {
+ m_pSearchActiveGroupBox->SetVisible( false );
+ }
+}
+
+void CBaseLobbyPanel::WriteGameSettingsControls()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+
+ SetMatchmakingModeBackground();
+
+ bool bLeader = BIsPartyLeader();
+ bool bInUIState = BIsPartyInUIState();
+
+ m_pJoinLateCheckButton->ToggleButton::SetSelected( GTFGCClientSystem()->GetSearchJoinLate() ); // !KLUDGE! call base to avoid firing the signal
+
+ bool bShowLateJoin = ShouldShowLateJoin();
+ m_pJoinLateCheckButton->SetVisible( bShowLateJoin && bLeader );
+ m_pJoinLateValueLabel->SetText( GTFGCClientSystem()->GetSearchJoinLate() ? "#TF_Matchmaking_SearchForAll" : "#TF_Matchmaking_SearchForNew" );
+ m_pJoinLateValueLabel->SetVisible( bShowLateJoin && !bLeader );
+ //m_pJoinLateValueLabel->SetEnabled( bInUIState );
+
+ m_pInviteButton->SetVisible( bInUIState && ( m_vecPlayers.Count() < k_nTFPartyMaxSize ) );
+
+ m_pChatTextEntry->RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the player list
+//-----------------------------------------------------------------------------
+void CBaseLobbyPanel::UpdatePlayerList()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !IsVisible() || !m_pContainer->IsVisible() )
+ return;
+
+ m_pChatPlayerList->ClearSelection();
+ m_pChatPlayerList->RemoveAll();
+ m_vecPlayers.RemoveAll();
+
+ bool bLadderGame = GTFGCClientSystem()->GetSearchMode() == TF_Matchmaking_LADDER &&
+ IsLadderGroup( (EMatchGroup)GTFGCClientSystem()->GetLadderType() );
+ const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( GetMatchGroup() );
+ EMMPenaltyPool ePenaltyPool = pMatchDesc ? pMatchDesc->m_params.m_ePenaltyPool : eMMPenaltyPool_Invalid;
+
+ if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
+ return;
+
+ // Locate party, if we have one.
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+
+ if ( pParty == NULL )
+ {
+ LobbyPlayerInfo p;
+ p.m_steamID = steamapicontext->SteamUser()->GetSteamID();
+ p.m_sName = steamapicontext->SteamFriends()->GetPersonaName();
+ p.m_bHasTicket = GTFGCClientSystem()->BLocalPlayerInventoryHasMvmTicket();
+ p.m_bSquadSurplus = GTFGCClientSystem()->GetLocalPlayerSquadSurplus();
+#ifdef USE_MVM_TOUR
+ int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex();
+ if ( idxTour < 0 || !GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &p.m_nBadgeLevel, &p.m_nCompletedChallenges ) )
+ {
+ p.m_nBadgeLevel = 0;
+ p.m_nCompletedChallenges = 0;
+ }
+#endif // USE_MVM_TOUR
+ p.m_pAvatarImage = NULL;
+ p.m_bHasCompetitiveAccess = GTFGCClientSystem()->BHasCompetitiveAccess();
+ CSOTFLadderData *pData = GetLocalPlayerLadderData( (EMatchGroup)GTFGCClientSystem()->GetLadderType() );
+ p.m_unLadderRank = ( pData ? pData->Obj().rank() : 1u );
+
+ uint32 unExperienceLevel = 1u;
+ const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL;
+ if ( pData && pProgressionDesc && pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_CASUAL )
+ {
+ LevelInfo_t levelInfo = pProgressionDesc->GetLevelForExperience( pData->Obj().experience() );
+ unExperienceLevel = levelInfo.m_nLevelNum;
+ }
+ p.m_unExperienceLevel = unExperienceLevel;
+
+ CEconGameAccountClient *pGameAccountClient = NULL;
+ if ( InventoryManager() && TFInventoryManager()->GetLocalTFInventory() && TFInventoryManager()->GetLocalTFInventory()->GetSOC() )
+ {
+ pGameAccountClient = TFInventoryManager()->GetLocalTFInventory()->GetSOC()->GetSingleton<CEconGameAccountClient>();
+ }
+
+ p.m_bIsBanned = false;
+ p.m_rtimeBanExpire = 0;
+ p.m_rtimeLowPriorityExpire = 0;
+ p.m_bIsLowPriority = false;
+ if ( pGameAccountClient && ePenaltyPool != eMMPenaltyPool_Invalid )
+ {
+ switch ( ePenaltyPool )
+ {
+ case eMMPenaltyPool_Casual:
+ p.m_rtimeBanExpire = pGameAccountClient->Obj().matchmaking_casual_ban_expiration();
+ p.m_rtimeLowPriorityExpire = pGameAccountClient->Obj().matchmaking_casual_low_priority_expiration();
+ p.m_bIsBanned = p.m_rtimeBanExpire > CRTime::RTime32TimeCur();
+ p.m_bIsLowPriority = p.m_rtimeLowPriorityExpire > CRTime::RTime32TimeCur();
+ break;
+ case eMMPenaltyPool_Ranked:
+ p.m_rtimeBanExpire = pGameAccountClient->Obj().matchmaking_ranked_ban_expiration();
+ p.m_rtimeLowPriorityExpire = pGameAccountClient->Obj().matchmaking_ranked_low_priority_expiration();
+ p.m_bIsBanned = p.m_rtimeBanExpire > CRTime::RTime32TimeCur();
+ p.m_bIsLowPriority = p.m_rtimeLowPriorityExpire > CRTime::RTime32TimeCur();
+ break;
+ default: Assert( false );
+ }
+ }
+
+ // !TEST!
+ //p.m_bIsBanned = true;
+
+ //for (int i = 0 ; i < 6 ; ++i) // !TEST!
+ m_vecPlayers.AddToTail( p );
+ }
+ else
+ {
+ for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i )
+ {
+ LobbyPlayerInfo p;
+ p.m_steamID = pParty->GetMember( i );
+ p.m_sName = steamapicontext->SteamFriends()->GetFriendPersonaName( p.m_steamID );
+ if ( p.m_sName.IsEmpty() )
+ continue;
+ p.m_bHasTicket = pParty->Obj().members( i ).owns_ticket();
+ p.m_nBadgeLevel = pParty->Obj().members( i ).badge_level();
+ p.m_nCompletedChallenges = pParty->Obj().members( i ).completed_missions();
+ p.m_bSquadSurplus = pParty->Obj().members( i ).squad_surplus();
+ p.m_pAvatarImage = NULL;
+ p.m_bIsBanned = pParty->Obj().members( i ).is_banned();
+ p.m_bHasCompetitiveAccess = pParty->Obj().members( i ).competitive_access();
+ p.m_unLadderRank = pParty->Obj().members( i ).ladder_rank();
+ p.m_rtimeBanExpire = pParty->Obj().matchmaking_ban_time();
+ p.m_rtimeLowPriorityExpire = pParty->Obj().matchmaking_low_priority_time();
+ p.m_bIsLowPriority = pParty->Obj().members( i ).is_low_priority();
+
+ uint32 unExperienceLevel = 1u;
+ const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL;
+ if ( pProgressionDesc && pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_CASUAL )
+ {
+ LevelInfo_t levelInfo = pProgressionDesc->GetLevelForExperience( pParty->Obj().members( i ).experience() );
+ unExperienceLevel = levelInfo.m_nLevelNum;
+ }
+ p.m_unExperienceLevel = unExperienceLevel;
+
+ if ( p.m_steamID == pParty->GetLeader() )
+ {
+ m_vecPlayers.AddToHead( p );
+ }
+ else
+ {
+ m_vecPlayers.AddToTail( p );
+ }
+ }
+ }
+
+ for( int i = 0; i < m_vecPlayers.Count(); ++i )
+ {
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ CUtlString sName;
+ if ( i == 0 && m_vecPlayers.Count() > 1 )
+ {
+ sName.Format( "%s (leader)", m_vecPlayers[i].m_sName.String() ); // !FIXME! Localize
+ }
+ else
+ {
+ sName = m_vecPlayers[i].m_sName;
+ }
+ pKeyValues->SetString( "name", sName );
+
+ pKeyValues->SetString( "steamid", CFmtStr( "%llu", m_vecPlayers[i].m_steamID.ConvertToUint64() ).Access() );
+ pKeyValues->SetInt( "badge_level", Max( 1U, m_vecPlayers[i].m_nBadgeLevel ) );
+
+ ApplyChatUserSettings( m_vecPlayers[ i ], pKeyValues );
+
+ pKeyValues->SetInt( "is_banned", ( m_vecPlayers[i].m_bIsBanned || m_vecPlayers[i].m_bIsLowPriority ) ? m_iImageIsBanned : 0 );
+
+ // See if the avatar's changed
+ int iAvatar = steamapicontext->SteamFriends()->GetSmallFriendAvatar( m_vecPlayers[i].m_steamID );
+ int iIndex = m_mapAvatarsToImageList.Find( iAvatar );
+ if ( iIndex == m_mapAvatarsToImageList.InvalidIndex() )
+ {
+ CAvatarImage *pImage = new CAvatarImage();
+ pImage->SetAvatarSteamID( m_vecPlayers[i].m_steamID );
+ pImage->SetDrawFriend( false ); // you can only invite friends, this isn't that useful
+ pImage->SetAvatarSize( m_iAvatarWidth, m_iAvatarWidth );
+ int iImageIndex = m_pImageList->AddImage( pImage );
+
+ iIndex = m_mapAvatarsToImageList.Insert( iAvatar, iImageIndex );
+ }
+ pKeyValues->SetInt( "avatar", m_mapAvatarsToImageList[iIndex] );
+ CAvatarImage *pAvIm = (CAvatarImage *)m_pImageList->GetImage( m_mapAvatarsToImageList[iIndex] );
+ pAvIm->UpdateFriendStatus();
+ m_vecPlayers[i].m_pAvatarImage = pAvIm;
+ if ( bLadderGame )
+ {
+ if ( !m_vecPlayers[i].m_bHasCompetitiveAccess )
+ {
+ pKeyValues->SetInt( "has_competitive_access", m_iImageNo );
+ }
+ }
+
+ pKeyValues->SetInt( "ladder_rank", m_vecPlayers[i].m_unLadderRank );
+ pKeyValues->SetInt( "experience_level", m_vecPlayers[i].m_unExperienceLevel );
+
+ int itemID = m_pChatPlayerList->AddItem( 0, pKeyValues );
+ m_pChatPlayerList->SetItemFont( itemID, m_fontPlayerListItem );
+ m_pChatPlayerList->SetItemFgColor( itemID, ( m_vecPlayers[i].m_bIsBanned || m_vecPlayers[i].m_bIsLowPriority ) ? s_colorBannedPlayerListItem : s_colorPlayerListItem );
+
+ pKeyValues->deleteThis();
+ }
+
+ // force the list to PerformLayout() now so we can update our medal images
+ m_pChatPlayerList->InvalidateLayout( true );
+
+ int iPanelCount = 0;
+ // This only works in 6v6 Comp and 12v12 Casual for now
+ if ( ( GetMatchGroup() == k_nMatchGroup_Ladder_6v6 ) || ( GetMatchGroup() == k_nMatchGroup_Casual_12v12 ) )
+ {
+ int nColumn = m_pChatPlayerList->GetColumnIndexByName( 0, "rank" );
+
+ for ( int nRow = 0; nRow < m_pChatPlayerList->GetItemCount(); nRow++ )
+ {
+ KeyValues *pKeyValues = m_pChatPlayerList->GetItemData( nRow );
+ if ( !pKeyValues )
+ continue;
+
+ CSteamID steamID = SteamIDFromDecimalString( pKeyValues->GetString( "steamid", "0" ) );
+ if ( !steamID.IsValid() )
+ continue;
+
+ uint32 unLevel = pKeyValues->GetInt( "ladder_rank" );
+ if ( GetMatchGroup() == k_nMatchGroup_Casual_12v12 )
+ {
+ unLevel = pKeyValues->GetInt( "experience_level" );
+ }
+
+ // Create a panel if we need one
+ if ( iPanelCount >= m_vecChatBadges.Count() )
+ {
+ m_vecChatBadges.AddToTail();
+ m_vecChatBadges[iPanelCount].m_pBadgeModel = vgui::SETUP_PANEL( new CTFBadgePanel( m_pChatPlayerList, "Model" ) );
+ m_vecChatBadges[iPanelCount].m_pBadgeModel->SetZPos( 9999 );
+ m_vecChatBadges[iPanelCount].m_nShownLevel = 0u;
+ }
+
+ // Move it into place and resize. This is terrible, but VGUI has forced my hand
+ int nX, nY, nWide, nTall;
+ m_pChatPlayerList->GetMaxCellBounds( nRow, nColumn, nX, nY, nWide, nTall );
+ int nSideLength = Max( nWide, nTall );
+ nX = ( nX + ( nWide / 2 ) ) - ( nSideLength / 2 );
+ nY = ( nY + ( nTall / 2 ) ) - ( nSideLength / 2 );
+
+ m_vecChatBadges[iPanelCount].m_pBadgeModel->SetBounds( nX, nY, nSideLength, nSideLength );
+
+ // Different dude in the slot or their level is different? Update the medal model
+ if ( m_vecChatBadges[iPanelCount].m_steamIDOwner != steamID ||
+ m_vecChatBadges[iPanelCount].m_nShownLevel != unLevel )
+ {
+ m_vecChatBadges[iPanelCount].m_steamIDOwner = steamID;
+
+ const LevelInfo_t& level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( unLevel );
+ m_vecChatBadges[iPanelCount].m_pBadgeModel->SetupBadge( pMatchDesc->m_pProgressionDesc, level );
+
+ wchar_t wszOutString[128];
+ char szLocalized[512];
+ wchar_t wszCount[16];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", level.m_nLevelNum );
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( pMatchDesc->m_pProgressionDesc->m_pszLevelToken );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, wszCount, g_pVGuiLocalize->Find( level.m_pszLevelTitle ) );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wszOutString, szLocalized, sizeof( szLocalized ) );
+
+ m_vecChatBadges[iPanelCount].m_pBadgeModel->SetTooltip( m_pToolTip, szLocalized );
+ m_vecChatBadges[iPanelCount].m_nShownLevel = level.m_nLevelNum;
+ m_vecChatBadges[iPanelCount].m_pBadgeModel->InvalidateLayout( true, true );
+ m_vecChatBadges[iPanelCount].m_pBadgeModel->SetVisible( true );
+ }
+
+ iPanelCount++;
+ }
+ }
+
+ for ( ; iPanelCount < m_vecChatBadges.Count(); ++iPanelCount )
+ {
+ m_vecChatBadges[iPanelCount].m_pBadgeModel->SetVisible( false );
+ m_vecChatBadges[iPanelCount].m_nShownLevel = 0u; // Will cause the badge to refresh when it gets a player
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void CBaseLobbyPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetResFile() );
+
+ m_pSearchActiveGroupBox = dynamic_cast<vgui::EditablePanel *>(FindChildByName( "SearchActiveGroupBox", true )); Assert( m_pSearchActiveGroupBox );
+ m_pSearchActiveTitleLabel = dynamic_cast<vgui::Label *>(FindChildByName( "SearchActiveTitle", true )); Assert( m_pSearchActiveTitleLabel );
+ m_pSearchActivePenaltyLabel = dynamic_cast<vgui::Label *>( FindChildByName( "PartyHasLowPriorityLabel", true ) ); Assert( m_pSearchActivePenaltyLabel );
+
+ m_pPartyHasLowPriority = dynamic_cast<vgui::EditablePanel *>( FindChildByName( "PartyHasLowPriorityGroupBox", true ) ); Assert( m_pPartyHasLowPriority );
+
+ m_pJoinLateCheckButton = dynamic_cast<vgui::CheckButton *>(FindChildByName( "JoinLateCheckButton", true )); Assert( m_pJoinLateCheckButton );
+ m_pJoinLateValueLabel = dynamic_cast<vgui::Label *>(FindChildByName( "JoinLateValueLabel", true )); Assert( m_pJoinLateValueLabel );
+
+ m_pInviteButton = dynamic_cast<vgui::Button *>(FindChildByName( "InviteButton", true )); Assert( m_pInviteButton );
+
+ delete m_pImageList;
+ m_pImageList = new vgui::ImageList( false );
+
+ m_iImageIsBanned = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_timeout_active", true ) );
+ m_pImageList->GetImage( m_iImageIsBanned )->SetSize( m_iBannedWidth, m_iBannedWidth );
+ m_iImageCheckBoxDisabled = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_disabled", true ) );
+ m_pImageList->GetImage( m_iImageCheckBoxDisabled )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) );
+ m_iImageCheckBoxYes = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_yes", true ) );
+ m_pImageList->GetImage( m_iImageCheckBoxYes )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) );
+ m_iImageCheckBoxNo = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_no", true ) );
+ m_pImageList->GetImage( m_iImageCheckBoxNo )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) );
+ m_iImageCheckBoxMixed = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_check_box_mixed", true ) );
+ m_pImageList->GetImage( m_iImageCheckBoxMixed )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) );
+ m_iImageRadioButtonYes = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_radio_button_yes", true ) );
+ m_pImageList->GetImage( m_iImageRadioButtonYes )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) );
+ m_iImageRadioButtonNo = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_radio_button_no", true ) );
+ m_pImageList->GetImage( m_iImageRadioButtonNo )->SetSize( m_iChallengeCheckBoxWidth, m_iChallengeCheckBoxWidth * ( 3.75f / 4.0f ) );
+ m_iImageNew = m_pImageList->AddImage( vgui::scheme()->GetImage( "new", true ) );
+ m_pImageList->GetImage( m_iImageNew )->SetSize( m_iNewWidth, m_iNewWidth * ( 3.75f / 4.0f ) );
+
+ m_iImageNo = m_pImageList->AddImage( vgui::scheme()->GetImage( "hud/vote_no", true ) );
+
+ m_mapAvatarsToImageList.RemoveAll();
+ m_pChatPlayerList->SetImageList( m_pImageList, false );
+ m_pChatPlayerList->SetVisible( true );
+
+
+ //
+ // Populate the challenge list
+ //
+ m_pJoinLateCheckButton->AddActionSignalTarget( this );
+ m_pInviteButton->AddActionSignalTarget( this );
+ m_pChatPlayerList->AddActionSignalTarget( this );
+
+ m_fontPlayerListItem = pScheme->GetFont( "DefaultSmall", true );
+
+ //
+ // Populate the player list
+ //
+
+ m_pChatPlayerList->SetVerticalScrollbar( false );
+ m_pChatPlayerList->RemoveAll();
+ m_pChatPlayerList->RemoveAllSections();
+ m_pChatPlayerList->AddSection( 0, "Players" );
+ m_pChatPlayerList->SetSectionAlwaysVisible( 0, true );
+ m_pChatPlayerList->SetSectionFgColor( 0, Color( 255, 255, 255, 255 ) );
+ m_pChatPlayerList->SetBgColor( Color( 0, 0, 0, 0 ) );
+ m_pChatPlayerList->SetBorder( NULL );
+ m_pChatPlayerList->SetClickable( false );
+ //m_pChatPlayerList->SetClickable( true ); // enable context menu to trade / kick?
+
+ bool bPartyLeader = BIsPartyLeader() && GCClientSystem()->BConnectedtoGC();
+
+ if ( bPartyLeader )
+ {
+ extern bool TF_IsHolidayActive( int eHoliday );
+ bool bHalloween = TF_IsHolidayActive( kHoliday_Halloween );
+ static bool bForcedOnce = false;
+ if ( bHalloween && !bForcedOnce )
+ {
+ GTFGCClientSystem()->SetQuickplayGameType( kGameCategory_Event247 );
+ bForcedOnce = true;
+ }
+ }
+
+ if ( bPartyLeader )
+ {
+ GTFGCClientSystem()->SetSearchJoinLate( tf_matchmaking_join_in_progress.GetBool() );
+ }
+}
+
+void CBaseLobbyPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ WriteGameSettingsControls();
+ UpdatePlayerList();
+}
+
+void CBaseLobbyPanel::OnItemContextMenu( vgui::Panel* panel )
+{
+ if ( m_iWritingPanel > 0 )
+ return;
+ m_pChatTextEntry->RequestFocus();
+ if ( panel == m_pChatPlayerList )
+ {
+ OnClickedOnPlayer();
+ return;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Command to launch the lobby UI, connecting to a particular lobby
+//-----------------------------------------------------------------------------
+static void CL_ConnectLobby( const CCommand &args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Warning( "connect_lobby missing LobbyID argument\n" );
+ return;
+ }
+
+ uint64 ulSteamID = 0;
+ sscanf( args.Arg( 1 ), "%lld", &ulSteamID );
+ CSteamID steamIDLobby( ulSteamID );
+ if ( !steamIDLobby.IsValid() || !steamIDLobby.IsLobby() )
+ {
+ Warning( "connect_lobby passed invalid LobbyID '%s'\n", args.Arg( 1 ) );
+ return;
+ }
+
+ GTFGCClientSystem()->AcceptFriendInviteToJoinLobby( steamIDLobby );
+}
+
+void OnSteamGameLobbyJoinRequested( GameLobbyJoinRequested_t *pInfo );
+
+static ConCommand connect_lobby_command( "connect_lobby", &CL_ConnectLobby, "<64-bit lobby ID> Accept friend invite, connecting to specified Steam lobby and joining the corresponding search party" );
+
+#endif // #ifdef ENABLE_GC_MATCHMAKING
diff --git a/game/client/tf/vgui/tf_lobbypanel.h b/game/client/tf/vgui/tf_lobbypanel.h
new file mode 100644
index 0000000..f7a998f
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel.h
@@ -0,0 +1,238 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef TF_LOBBYPANEL_H
+#define TF_LOBBYPANEL_H
+
+
+#include "cbase.h"
+//#include "tf_pvelobbypanel.h"
+#include "game/client/iviewport.h"
+#include "vgui_controls/TextEntry.h"
+#include "tf_matchmaking_shared.h"
+#include "vgui_controls/RichText.h"
+#include "vgui_controls/CheckButton.h"
+#include "tf_gc_client.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+class CBaseLobbyContainerFrame;
+class CAvatarImage;
+class CTFBadgePanel;
+class CMainMenuToolTip;
+
+class CBaseLobbyPanel : public vgui::PropertySheet, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CBaseLobbyPanel, PropertySheet );
+
+ friend class CBaseLobbyContainerFrame;
+public:
+ CBaseLobbyPanel( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer );
+
+ virtual ~CBaseLobbyPanel();
+
+ //
+ // Panel overrides
+ //
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+
+ //
+ // CGameEventListener overrides
+ //
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ int NumPlayersInParty( void ) { return m_vecPlayers.Count(); }
+
+ void ToggleJoinLateCheckButton( void ) { m_pJoinLateCheckButton->SetSelected( !m_pJoinLateCheckButton->IsSelected() );}
+
+ bool IsPartyActiveGroupBoxVisible() { return m_pPartyActiveGroupBox != NULL && m_pPartyActiveGroupBox->IsVisible(); }
+ bool IsAnyoneBanned( RTime32 &rtimeExpire ) const;
+ bool IsAnyoneLowPriority( RTime32 &rtimeExpire ) const;
+
+ void UpdateControls();
+
+ virtual EMatchGroup GetMatchGroup( void ) const = 0;
+
+protected:
+
+ virtual void WriteGameSettingsControls();
+
+ MESSAGE_FUNC_PTR( OnItemLeftClick, "ItemLeftClick", panel );
+ MESSAGE_FUNC_PTR( OnItemContextMenu, "ItemContextMenu", panel );
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel );
+ MESSAGE_FUNC_PARAMS( OnTradeWithUser, "TradeWithUser", params );
+
+ CPanelAnimationVarAliasType( int, m_iNewWidth, "challenge_new_width", "19", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iChallengeCheckBoxWidth, "challenge_check_box_width", "8", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iAvatarWidth, "avatar_width", "16", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iPlayerNameWidth, "player_name_width", "110", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iBannedWidth, "squad_surplus_width", "12", "proportional_int" );
+
+ // Sectioned list panels are the worst
+ struct ChatModelPanel_t
+ {
+ CTFBadgePanel* m_pBadgeModel;
+ CSteamID m_steamIDOwner;
+ uint32 m_nShownLevel;
+ };
+
+ CUtlVector< ChatModelPanel_t > m_vecChatBadges;
+ vgui::SectionedListPanel *m_pChatPlayerList;
+ vgui::ImageList *m_pImageList;
+ CBaseLobbyContainerFrame *m_pContainer;
+
+ int m_iWritingPanel;
+
+ int m_iImageIsBanned;
+ int m_iImageNew;
+ int m_iImageNo;
+ int m_iImageRadioButtonYes;
+ int m_iImageRadioButtonNo;
+ int m_iImageCheckBoxDisabled;
+ int m_iImageCheckBoxYes;
+ int m_iImageCheckBoxNo;
+ int m_iImageCheckBoxMixed;
+
+ struct LobbyPlayerInfo
+ {
+ CSteamID m_steamID;
+ CUtlString m_sName;
+ bool m_bHasTicket;
+ bool m_bSquadSurplus;
+ uint32 m_nBadgeLevel;
+ CAvatarImage *m_pAvatarImage;
+ uint32 m_nCompletedChallenges; // bitmask of badge slots (not related to the challenge index in the schema!)
+ bool m_bIsBanned;
+ RTime32 m_rtimeBanExpire;
+ RTime32 m_rtimeLowPriorityExpire;
+ bool m_bHasCompetitiveAccess;
+ uint32 m_unLadderRank;
+ bool m_bIsLowPriority;
+ uint32 m_unExperienceLevel;
+ };
+
+ const LobbyPlayerInfo* GetLobbyPlayerInfo( CSteamID &steamID ) const;
+
+ virtual void OnThink() OVERRIDE
+ {
+ BaseClass::OnThink();
+ WriteStatusControls();
+
+ if ( gpGlobals->curtime > m_flRefreshPlayerListTime )
+ {
+ UpdatePlayerList();
+ m_flRefreshPlayerListTime = gpGlobals->curtime + 1.f;
+ }
+ }
+
+
+private:
+
+ void SetMatchmakingModeBackground();
+ virtual const char* GetResFile() const = 0;
+ virtual void ApplyChatUserSettings( const LobbyPlayerInfo& player, KeyValues* pSettings ) const = 0;
+ void OnClickedOnPlayer();
+
+ class ChatTextEntry : public vgui::TextEntry
+ {
+ public:
+ ChatTextEntry( vgui::Panel *parent, const char *name ) : vgui::TextEntry( parent, name )
+ {
+ SetCatchEnterKey( true );
+ SetAllowNonAsciiCharacters( true );
+ SetDrawLanguageIDAtLeft( true );
+ }
+
+ virtual void OnKeyCodeTyped(vgui::KeyCode code)
+ {
+ if ( code == KEY_ENTER || code == KEY_PAD_ENTER )
+ {
+ if ( GetTextLength() > 0 )
+ {
+ int nBufSizeBytes = ( GetTextLength() + 4 ) * sizeof( wchar_t );
+ wchar_t *wText = (wchar_t *)stackalloc( nBufSizeBytes );
+ GetText( wText, nBufSizeBytes );
+ TextEntry::SetText("");
+
+ // Convert to UTF8, which is really what we should
+ // use for everything
+ int nUtf8BufferSizeBytes = nBufSizeBytes * 2;
+ char *szText = (char *)stackalloc( nUtf8BufferSizeBytes );
+ V_UnicodeToUTF8( wText, szText, nUtf8BufferSizeBytes );
+ GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_UserChat, szText );
+ }
+ }
+ else if ( code == KEY_TAB )
+ {
+ // Ignore tab, otherwise vgui will screw up the focus.
+ return;
+ }
+ else
+ {
+ vgui::TextEntry::OnKeyCodeTyped( code );
+ }
+ }
+ };
+
+ class ChatLog : public vgui::RichText
+ {
+ public:
+ ChatLog( vgui::Panel *parent, const char *name ) : vgui::RichText( parent, name )
+ {
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ vgui::RichText::ApplySchemeSettings(pScheme);
+
+ vgui::HFont hFont = pScheme->GetFont( "ChatMiniFont" );
+ SetFont( hFont );
+ }
+ };
+
+ CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels;
+
+ vgui::EditablePanel *m_pSearchActiveGroupBox;
+ vgui::Label *m_pSearchActiveTitleLabel;
+ vgui::Label *m_pSearchActivePenaltyLabel;
+ vgui::EditablePanel *m_pPartyHasLowPriority;
+
+ vgui::CheckButton *m_pJoinLateCheckButton;
+ vgui::Label *m_pJoinLateValueLabel;
+
+ vgui::EditablePanel *m_pPartyActiveGroupBox;
+ vgui::Button *m_pInviteButton;
+ ChatLog *m_pChatLog;
+ ChatTextEntry *m_pChatTextEntry;
+
+ int m_iImageAvatars[MAX_PLAYERS+1];
+ CUtlMap<int,int> m_mapAvatarsToImageList;
+
+ vgui::HFont m_fontPlayerListItem;
+
+ CSOTFParty_State m_eCurrentPartyState;
+ float m_flRefreshPlayerListTime;
+
+ CMainMenuToolTip* m_pToolTip;
+
+ void UpdatePlayerList();
+ void WriteStatusControls(); // MM status
+ virtual bool ShouldShowLateJoin() const = 0;
+
+ CUtlVector<LobbyPlayerInfo> m_vecPlayers;
+
+ CCallback<CBaseLobbyPanel, PersonaStateChange_t, false> m_sPersonaStateChangedCallback;
+
+ void OnPersonaStateChanged( PersonaStateChange_t *info )
+ {
+ UpdatePlayerList();
+ }
+};
+
+#endif //TF_LOBBYPANEL_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_lobbypanel_casual.cpp b/game/client/tf/vgui/tf_lobbypanel_casual.cpp
new file mode 100644
index 0000000..bbb0e54
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel_casual.cpp
@@ -0,0 +1,621 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+
+#include "tf_party.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+
+#include "iclientmode.h"
+#include <vgui_controls/AnimationController.h>
+
+#include "tf_lobbypanel_casual.h"
+#include "tf_lobby_container_frame_casual.h"
+#include "tf_matchmaking_shared.h"
+
+#include "tf_hud_mainmenuoverride.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern Color s_colorChallengeForegroundEnabled;
+extern Color s_colorChallengeForegroundDisabled;
+extern Color s_colorChallengeHeader;
+
+extern const char *s_pszMatchGroups[];
+
+ConVar tf_show_maps_details_explanation_session( "tf_show_maps_details_explanation_session", "0", FCVAR_HIDDEN );
+ConVar tf_show_maps_details_explanation_count( "tf_show_maps_details_explanation_count", "2", FCVAR_ARCHIVE | FCVAR_HIDDEN );
+
+class CLobbyPanel_Casual;
+
+class CCasualCategory : public CExpandablePanel
+{
+ DECLARE_CLASS_SIMPLE( CCasualCategory, CExpandablePanel );
+
+public:
+ CCasualCategory( Panel *parent, const char *panelName, EGameCategory eCategory, Panel* pSignalHandler )
+ : BaseClass( parent, panelName )
+ , m_eCategory( eCategory )
+ , pToggleButton( NULL )
+ , m_mapMapPanels( DefLessFunc( uint32 ) )
+ , m_pSignalHandler( pSignalHandler )
+ {}
+
+ ~CCasualCategory()
+ {
+ // Clear out the old map entries
+ ClearMapEntries();
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/MatchmakingCategoryPanel.res" );
+
+ const SchemaGameCategory_t* pCategory = GetItemSchema()->GetGameCategory( m_eCategory );
+ Assert( pCategory );
+ if ( !pCategory )
+ return;
+
+ EditablePanel* pTopContainer = FindControl< EditablePanel >( "TopContainer", true );
+ if ( pTopContainer )
+ {
+ // Set our dialog variables
+ pTopContainer->SetDialogVariable( "title_token", g_pVGuiLocalize->Find( pCategory->m_pszLocalizedName ) );
+ pTopContainer->SetDialogVariable( "desc_token", g_pVGuiLocalize->Find( pCategory->m_pszLocalizedDesc ) );
+ }
+
+ ImagePanel* pImagePanel = FindControl< ImagePanel >( "BGImage", true );
+ if ( pImagePanel && pCategory && pCategory->m_pszListImage )
+ {
+ pImagePanel->SetImage( pCategory->m_pszListImage );
+ }
+
+ // Clear out the old map entries
+ ClearMapEntries();
+
+ EditablePanel* pMapsContainer = FindControl< EditablePanel >( "MapsContainer", true );
+
+ if ( pMapsContainer )
+ {
+ int nYPos = 16;
+
+ FOR_EACH_VEC( pCategory->m_vecEnabledMaps, i )
+ {
+ const MapDef_t* pMap = pCategory->m_vecEnabledMaps[ i ];
+
+ // Load control settings
+ EditablePanel* pMapEntry = new EditablePanel( pMapsContainer, "MatchmakingCategoryMapPanel" );
+ pMapEntry->LoadControlSettings( "resource/ui/MatchmakingCategoryMapPanel.res" );
+ pMapEntry->SetAutoDelete( false );
+
+ CExCheckButton* pCheckButton = pMapEntry->FindControl< CExCheckButton >( "MapCheckbutton" );
+ if ( pCheckButton )
+ {
+ KeyValues* pKVData = new KeyValues( "data" );
+ pKVData->SetInt( "map_index", pMap->m_nDefIndex );
+ pCheckButton->SetData( pKVData );
+ pCheckButton->AddActionSignalTarget( m_pSignalHandler );
+ }
+
+ // Add to map so we can look it up
+ m_mapMapPanels.Insert( pMap->m_nDefIndex, pMapEntry );
+
+ // Update label
+ pMapEntry->SetDialogVariable( "title_token", g_pVGuiLocalize->Find( pMap->pszMapNameLocKey ) );
+
+ bool bOdd = i % 2 == 1;
+ int nXPos = bOdd ? GetWide() * 0.5f : 0;
+ pMapEntry->SetPos( nXPos, nYPos );
+
+ nYPos += bOdd || i == pCategory->m_vecEnabledMaps.Count() - 1 ? pMapEntry->GetTall() : 0;
+ }
+
+ pMapsContainer->SetTall( nYPos + 10 );
+ pMapsContainer->SetAutoResize( PIN_BOTTOMRIGHT, Panel::AUTORESIZE_NO, 0, 0, 0, 0 );
+
+ // We want to be able to expand to this height
+ m_nExpandedHeight = nYPos + m_nCollapsedHeight;
+ }
+
+ // Snag the button for later
+ pToggleButton = FindControl< CExImageButton >( "EntryToggleButton", true );
+ }
+
+ virtual void OnToggleCollapse( bool bIsExpanded ) OVERRIDE
+ {
+ if ( bIsExpanded && !tf_show_maps_details_explanation_session.GetBool() && tf_show_maps_details_explanation_count.GetInt() > 0 )
+ {
+ tf_show_maps_details_explanation_count.SetValue( tf_show_maps_details_explanation_count.GetInt() - 1 );
+ tf_show_maps_details_explanation_session.SetValue( true );
+
+ // I dont care anymore
+ GetParent()->GetParent()->GetParent()->GetParent()->GetParent()->GetParent()->OnCommand( "show_maps_details_explanation" );
+ }
+
+ BaseClass::OnToggleCollapse( bIsExpanded );
+ }
+
+ virtual void PerformLayout() OVERRIDE
+ {
+ BaseClass::PerformLayout();
+
+ SetControlVisible( "EntryToggleButtonCollapsed", !BIsExpanded(), true );
+ SetControlVisible( "EntryToggleButtonExpanded", BIsExpanded(), true );
+
+ if ( pToggleButton )
+ {
+ pToggleButton->SetImageArmed( BIsExpanded() ? "/pve/sell_selected" : "/pve/buy_selected" );
+ pToggleButton->SetImageDefault( BIsExpanded() ? "/pve/sell_disabled" : "/pve/buy_disabled" );
+ }
+
+ // Update progress bars
+ FOR_EACH_MAP_FAST( m_mapMapPanels, i )
+ {
+ int nMapIndex = m_mapMapPanels.Key( i );
+ auto healthData = GTFGCClientSystem()->GetHealthDataForMap( nMapIndex );
+
+ // Update bars with latest health data
+ ProgressBar* pProgress = m_mapMapPanels[ i ]->FindControl< ProgressBar >( "HealthProgressBar", true );
+ if ( pProgress )
+ {
+ pProgress->SetProgress( healthData.m_flRatio );
+ pProgress->SetFgColor( healthData.m_colorBar );
+ }
+ }
+ }
+
+ void SetCheckButtonState( uint32 nMapDefIndex, bool bSelected, bool bClickable )
+ {
+ auto idx = m_mapMapPanels.Find( nMapDefIndex );
+ if ( idx != m_mapMapPanels.InvalidIndex() )
+ {
+ EditablePanel* pMapEntry = m_mapMapPanels[ idx ];
+ CExCheckButton* pMapCheckButton = pMapEntry->FindControl< CExCheckButton >( "MapCheckbutton", true );
+ if ( pMapCheckButton )
+ {
+ // Update button state without sending signals
+ pMapCheckButton->SetSilentMode( true );
+ pMapCheckButton->SetCheckButtonCheckable( true );
+ pMapCheckButton->SetSelected( bSelected );
+ pMapCheckButton->SetCheckButtonCheckable( bClickable );
+ pMapCheckButton->SetSilentMode( false );
+ }
+
+ if ( g_pClientMode && g_pClientMode->GetViewport() )
+ {
+ if ( bSelected )
+ {
+ g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pMapEntry, "HealthProgressBar_NotSelected" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapEntry, "HealthProgressBar_Selected" );
+ }
+ else
+ {
+ g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pMapEntry, "HealthProgressBar_Selected" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapEntry, "HealthProgressBar_NotSelected" );
+ }
+ }
+ }
+ }
+
+private:
+ void ClearMapEntries()
+ {
+ // Clear out the old map entries
+ FOR_EACH_MAP_FAST( m_mapMapPanels, i )
+ {
+ m_mapMapPanels[ i ]->MarkForDeletion();
+ }
+ m_mapMapPanels.Purge();
+ }
+
+ const EGameCategory m_eCategory;
+ CExImageButton* pToggleButton;
+ Panel* m_pSignalHandler;
+ CUtlMap< uint32, EditablePanel* > m_mapMapPanels;
+};
+
+
+static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID )
+{
+ const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
+ V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes );
+}
+
+CLobbyPanel_Casual::CLobbyPanel_Casual( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer )
+ : CBaseLobbyPanel( pParent, pLobbyContainer )
+ , m_fontCategoryListItem( 0 )
+ , m_fontGroupHeader( 0 )
+ , m_flCompetitiveRankProgress( -1.f )
+ , m_flCompetitiveRankPrevProgress( -1.f )
+ , m_flRefreshPlayerListTime( -1.f )
+ , m_bCompetitiveRankChangePlayedSound( false )
+ , m_mapGroupPanels( DefLessFunc( EMatchmakingGroupType ) )
+ , m_mapCategoryPanels( DefLessFunc( EGameCategory ) )
+ , m_flNextCasualStatsUpdateTime( 0.f )
+ , m_bCriteriaDirty( true )
+{
+ GTFGCClientSystem()->LoadCasualSearchCriteria();
+
+ ListenForGameEvent( "matchmaker_stats_updated" );
+}
+
+CLobbyPanel_Casual::~CLobbyPanel_Casual()
+{
+ delete m_pImageList;
+ m_pImageList = NULL;
+}
+
+bool CLobbyPanel_Casual::ShouldShowLateJoin() const
+{
+ return false; // We force the option on, so no need to show the checkbox
+}
+
+void CLobbyPanel_Casual::ApplyChatUserSettings( const CBaseLobbyPanel::LobbyPlayerInfo &player, KeyValues *pKV ) const
+{
+ pKV->SetInt( "has_ticket", 0 );
+ pKV->SetInt( "squad_surplus", 0 );
+}
+
+void CLobbyPanel_Casual::WriteGameSettingsControls()
+{
+ BaseClass::WriteGameSettingsControls();
+
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+
+ ++m_iWritingPanel;
+
+ TF_Matchmaking_WizardStep eWizardStep = GTFGCClientSystem()->GetWizardStep();
+
+ m_bCriteriaDirty = true;
+
+ // competitive
+ SetControlVisible( "GameModesContainer", eWizardStep == TF_Matchmaking_WizardStep_CASUAL );
+
+ --m_iWritingPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+EMatchGroup CLobbyPanel_Casual::GetMatchGroup( void ) const
+{
+ return k_nMatchGroup_Casual_12v12; // Force to 12v12 for now
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Casual::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( Plat_FloatTime() > m_flNextCasualStatsUpdateTime )
+ {
+ m_flNextCasualStatsUpdateTime = Plat_FloatTime() + 60.f;
+ GTFGCClientSystem()->RequestMatchMakerStats();
+ }
+
+ if ( m_bCriteriaDirty )
+ {
+ WriteCategories();
+ }
+}
+
+void CLobbyPanel_Casual::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "matchmaker_stats_updated" ) )
+ {
+ m_bCriteriaDirty = true;
+
+ return;
+ }
+
+ BaseClass::FireGameEvent( event );
+}
+
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Casual::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) );
+ if ( pMMOverride )
+ {
+ CLobbyContainerFrame_Casual *pCasualFrame = pMMOverride->GetCasualLobbyPanel();
+ if ( pCasualFrame )
+ {
+ CMainMenuToolTip *pToolTip = pCasualFrame->GetTooltipPanel();
+ if ( pToolTip )
+ {
+ CExImageButton *pButton = FindControl<CExImageButton>( "RestoreCasualSearchCriteria", true );
+ if ( pButton )
+ {
+ pButton->SetTooltip( pToolTip, "#TF_Casual_Tip_Restore" );
+ }
+
+ pButton = FindControl<CExImageButton>( "SaveCasualSearchCriteria", true );
+ if ( pButton )
+ {
+ pButton->SetTooltip( pToolTip, "#TF_Casual_Tip_Save" );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Casual::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ m_mapGroupPanels.Purge();
+ m_mapCategoryPanels.Purge();
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ int nAvatarWidth = ( ( m_iAvatarWidth * 5 / 4 ) + 1 );
+ int nExtraWidth = ( m_pChatPlayerList->GetWide() - ( 2 * nAvatarWidth ) - m_iPlayerNameWidth - m_iBannedWidth );
+
+ m_pChatPlayerList->AddColumnToSection( 0, "avatar", "#TF_Players", vgui::SectionedListPanel::COLUMN_IMAGE, nAvatarWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "name", "", 0, m_iPlayerNameWidth + nExtraWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "is_banned", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iBannedWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "rank", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, nAvatarWidth );
+ m_pChatPlayerList->SetDrawHeaders( false );
+ m_fontCategoryListItem = pScheme->GetFont( "HudFontSmallest", true );
+ m_fontGroupHeader = pScheme->GetFont( "HudFontSmallestBold", true );
+}
+
+void SelectCategory( EGameCategory eCategory, bool bSelected )
+{
+ auto pCat = GetItemSchema()->GetGameCategory( eCategory );
+ if ( pCat )
+ {
+ FOR_EACH_VEC( pCat->m_vecEnabledMaps, i )
+ {
+ if ( pCat->m_vecEnabledMaps[ i ] )
+ {
+ GTFGCClientSystem()->SelectCasualMap( pCat->m_vecEnabledMaps[ i ]->m_nDefIndex, bSelected );
+ }
+ }
+ }
+}
+
+void SelectGroup( EMatchmakingGroupType eGroup, bool bSelected )
+{
+ auto pGroup = GetItemSchema()->GetMMGroup( eGroup );
+ if ( pGroup )
+ {
+ FOR_EACH_VEC( pGroup->m_vecModes, i )
+ {
+ SelectCategory( pGroup->m_vecModes[ i ]->m_eGameCategory, bSelected );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A check box got checked! Search criteria
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Casual::OnCheckButtonChecked( vgui::Panel* panel )
+{
+ if ( m_iWritingPanel > 0 )
+ return;
+
+ CExCheckButton* pCheckButton = dynamic_cast< CExCheckButton* >( panel );
+ if ( pCheckButton )
+ {
+ bool bSelected = pCheckButton->IsSelected();
+
+ pCheckButton->RemoveActionSignalTarget( this );
+ pCheckButton->SetSelected( false );
+ pCheckButton->AddActionSignalTarget( this );
+
+ if ( BIsPartyLeader() && BIsPartyInUIState() )
+ {
+ int nMapIndex = pCheckButton->GetData()->GetInt( "map_index", -1 );
+ int nCategoryIndex = pCheckButton->GetData()->GetInt( "category_index", -1 );
+ int nGroupIndex = pCheckButton->GetData()->GetInt( "group_index", -1 );
+ Assert( nCategoryIndex >= 0 || nGroupIndex >= 0 || nMapIndex >= 0 );
+ if ( nGroupIndex >= 0 )
+ {
+ EMatchmakingGroupType eGroup = EMatchmakingGroupType( nGroupIndex );
+ SelectGroup( eGroup, bSelected );
+ }
+ else if ( nCategoryIndex >= 0 )
+ {
+ EGameCategory eCategory = EGameCategory( nCategoryIndex );
+ SelectCategory( eCategory, bSelected );
+ }
+ else if ( nMapIndex >= 0 )
+ {
+ GTFGCClientSystem()->SelectCasualMap( nMapIndex, bSelected );
+ }
+ }
+
+ m_bCriteriaDirty = true;
+ }
+
+ BaseClass::OnCheckButtonChecked( panel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Write all the category controls settings
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Casual::WriteCategories( void )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !m_bCriteriaDirty )
+ return;
+
+ m_bCriteriaDirty = false;
+
+ bool bLeader = BIsPartyLeader();
+ bool bInUIState = BIsPartyInUIState();
+ bool bClickable = bLeader && bInUIState;
+ m_bHasAMapSelected = false;
+
+ CScrollableList* pScrollableList = FindControl< CScrollableList >( "GameModesList", true );
+ if ( !pScrollableList )
+ return;
+
+ CUtlVector< const MapDef_t* > vecSelectedMaps;
+
+ pScrollableList->SetMouseInputEnabled( true );
+
+ const MMGroupMap_t& mapMMGroups = GetItemSchema()->GetMMGroupMap();
+
+ int nCategory = 0;
+ // Go through every MM group, and create a label for it and all its categories underneath
+ FOR_EACH_MAP( mapMMGroups, i )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - FOR_EACH_MAP( mapMMGroups, i )", __FUNCTION__ );
+
+ const SchemaMMGroup_t* pCat = mapMMGroups[ i ];
+
+ if ( !pCat->m_bitsValidMMGroups.IsBitSet( GetMatchGroup() ) )
+ {
+ continue;
+ }
+
+ if ( !pCat->IsCategoryValid() )
+ {
+ continue;
+ }
+
+ ++nCategory;
+
+ bool bGroupSelected = false;
+
+ EditablePanel* pGroupPanel = NULL;
+ CExCheckButton* pTitleLabel = NULL;
+ auto idx = m_mapGroupPanels.Find( pCat->m_eMMGroup );
+ // Create the check button/label if not created yet
+ if ( idx == m_mapGroupPanels.InvalidIndex() )
+ {
+ pGroupPanel = new EditablePanel( pScrollableList, "MatchmakingGroupPanel" );
+ pGroupPanel->LoadControlSettings( "resource/ui/MatchMakingGroupPanel.res" );
+ pTitleLabel = pGroupPanel->FindControl< CExCheckButton >( "Checkbutton" );
+ pTitleLabel->SetText( pCat->m_pszLocalizedName );
+
+ KeyValues* pKVData = new KeyValues( "data" );
+ pKVData->SetInt( "group_index", pCat->m_eMMGroup );
+ pTitleLabel->SetData( pKVData );
+
+ pScrollableList->AddPanel( pGroupPanel, nCategory > 1 ? 2 : -4 );
+ m_mapGroupPanels.Insert( pCat->m_eMMGroup, pGroupPanel );
+ }
+ else
+ {
+ pGroupPanel = (EditablePanel*)m_mapGroupPanels[ idx ];
+ pTitleLabel = pGroupPanel->FindControl< CExCheckButton >( "Checkbutton" );
+ }
+
+ // Category items.
+ FOR_EACH_VEC( pCat->m_vecModes, j )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - FOR_EACH_VEC( pCat->m_vecModes, j )", __FUNCTION__ );
+
+ const SchemaGameCategory_t* pCategory = pCat->m_vecModes[ j ];
+
+ if ( !pCategory->PassesRestrictions() )
+ {
+ continue;
+ }
+
+ CCasualCategory* pListEntry = NULL;
+ auto idxCat = m_mapCategoryPanels.Find( pCategory->m_eGameCategory );
+ // Create the entry if it doesnt exist yet
+ if ( idxCat == m_mapCategoryPanels.InvalidIndex() )
+ {
+ pListEntry = new CCasualCategory( pScrollableList, "MatchmakingCategoryPanel", pCategory->m_eGameCategory, this );
+ pListEntry->MakeReadyForUse();
+
+ pScrollableList->AddPanel( pListEntry, j > 0 ? 5 : 0 );
+ m_mapCategoryPanels.Insert( pCategory->m_eGameCategory, pListEntry );
+ }
+ else
+ {
+ pListEntry = (CCasualCategory*)m_mapCategoryPanels[ idxCat ];
+ }
+
+ bool bCatSelected = false;
+
+ FOR_EACH_VEC( pCategory->m_vecEnabledMaps, k )
+ {
+ bool bMapSelected = GTFGCClientSystem()->IsCasualMapSelected( pCategory->m_vecEnabledMaps[ k ]->m_nDefIndex );
+ m_bHasAMapSelected |= bMapSelected;
+ bCatSelected = bCatSelected | bMapSelected;
+ bGroupSelected = bGroupSelected | bCatSelected;
+
+ // Update map check button state
+ pListEntry->SetCheckButtonState( pCategory->m_vecEnabledMaps[ k ]->m_nDefIndex, bMapSelected, bLeader );
+
+ // We're going to use this to setup the tooltip for total selected maps
+ if ( bMapSelected )
+ {
+ vecSelectedMaps.AddToTail( pCategory->m_vecEnabledMaps[ k ] );
+ }
+ }
+
+ if ( bCatSelected )
+ {
+ g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pListEntry, "CasualCategory_NotSelected" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pListEntry, "CasualCategory_Selected" );
+ }
+ else
+ {
+ g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pListEntry, "CasualCategory_Selected" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pListEntry, "CasualCategory_NotSelected" );
+ }
+
+ pListEntry->InvalidateLayout();
+
+ // Update the check button within the list entry
+ CExCheckButton* pCheckButton = pListEntry->FindControl< CExCheckButton >( "CheckButton", true );
+ if ( pCheckButton )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - if ( pCheckButton )", __FUNCTION__ );
+
+ pCheckButton->RemoveActionSignalTarget( this ); // So we dont endlessly loop by checking
+ pCheckButton->SetCheckButtonCheckable( true ); // So we can potentially check it on the next line
+ pCheckButton->SetSelected( bCatSelected );
+ pCheckButton->SetCheckButtonCheckable( bLeader );
+ pCheckButton->AddActionSignalTarget( this ); // So that we get user check messages
+
+ KeyValues* pKVData = new KeyValues( "data" );
+ pKVData->SetInt( "category_index", pCategory->m_eGameCategory );
+ pCheckButton->SetData( pKVData );
+ pCheckButton->SetMouseInputEnabled( bClickable );
+ }
+ }
+
+ // Update check button state
+ pTitleLabel->RemoveActionSignalTarget( this );
+ pTitleLabel->SetCheckButtonCheckable( true );
+ pTitleLabel->SetSelected( bGroupSelected );
+ pTitleLabel->SetCheckButtonCheckable( bLeader );
+ pTitleLabel->AddActionSignalTarget( this );
+ pGroupPanel->SetMouseInputEnabled( bClickable );
+ pTitleLabel->SetMouseInputEnabled( bClickable ); // Update clickability
+ }
+
+ EditablePanel* pPlayListPanel = FindControl< EditablePanel >( "PlaylistBGPanel", true );
+ if ( pPlayListPanel )
+ {
+ // Setup the "X maps selected" label
+ const char* pszToken = vecSelectedMaps.Count() == 1 ? "TF_Casual_SelectedMaps_Singular" : "TF_Casual_SelectedMaps_Plural";
+ pPlayListPanel->SetDialogVariable( "selected_maps_count", LocalizeNumberWithToken( pszToken, vecSelectedMaps.Count() ) );
+ }
+
+ m_pContainer->SetNextButtonEnabled( m_bHasAMapSelected );
+}
diff --git a/game/client/tf/vgui/tf_lobbypanel_casual.h b/game/client/tf/vgui/tf_lobbypanel_casual.h
new file mode 100644
index 0000000..e7c882c
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel_casual.h
@@ -0,0 +1,77 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#ifndef TF_LOBBYPANEL_CASUAL_H
+#define TF_LOBBYPANEL_CASUAL_H
+
+#include "cbase.h"
+#include "game/client/iviewport.h"
+#include "tf_lobbypanel.h"
+#include "tf_leaderboardpanel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+class CBaseLobbyPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CLobbyPanel_Casual : public CBaseLobbyPanel
+{
+ DECLARE_CLASS_SIMPLE( CLobbyPanel_Casual, CBaseLobbyPanel );
+
+public:
+ CLobbyPanel_Casual( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer );
+ virtual ~CLobbyPanel_Casual();
+
+ //
+ // Panel overrides
+ //
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+
+ virtual EMatchGroup GetMatchGroup( void ) const OVERRIDE;
+
+ virtual void OnThink() OVERRIDE;
+
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+private:
+
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel );
+
+ CPanelAnimationVarAliasType( int, m_iCategorySpacer, "category_spacer", "4", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iCategoryNameWidth, "category_name_width", "190", "proportional_int" );
+
+ virtual bool ShouldShowLateJoin() const OVERRIDE;
+ virtual void ApplyChatUserSettings( const LobbyPlayerInfo &player,KeyValues *pKV ) const OVERRIDE;
+ virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyPanel_Casual.res"; }
+
+ void WriteGameSettingsControls() OVERRIDE;
+
+ void WriteCategories( void );
+
+ CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels;
+
+ vgui::HFont m_fontCategoryListItem;
+ vgui::HFont m_fontGroupHeader;
+
+ float m_flCompetitiveRankProgress;
+ float m_flCompetitiveRankPrevProgress;
+ float m_flRefreshPlayerListTime;
+ bool m_bCompetitiveRankChangePlayedSound;
+ float m_flNextCasualStatsUpdateTime;
+
+ bool m_bHasAMapSelected;
+
+ CUtlMap< EMatchmakingGroupType, Panel* > m_mapGroupPanels;
+ CUtlMap< EGameCategory, Panel* > m_mapCategoryPanels;
+
+ bool m_bCriteriaDirty;
+};
+
+#endif //TF_LOBBYPANEL_COMP_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_lobbypanel_comp.cpp b/game/client/tf/vgui/tf_lobbypanel_comp.cpp
new file mode 100644
index 0000000..73577af
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel_comp.cpp
@@ -0,0 +1,826 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+
+#include "tf_party.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+
+#include "tf_lobbypanel_comp.h"
+#include "tf_lobby_container_frame_comp.h"
+
+#include "vgui/ISystem.h"
+
+#include "tf_streams.h"
+#include "tf_badge_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+ConVar tf_mm_ladder_ui_last_rating_change( "tf_mm_ladder_ui_last_rating_change", "0", FCVAR_HIDDEN | FCVAR_ARCHIVE, "Track last match skillrating change for UI." );
+ConVar tf_mm_ladder_ui_last_rating_time( "tf_mm_ladder_ui_last_rating_time", "-1", FCVAR_HIDDEN | FCVAR_ARCHIVE, "Track last match skillrating change time for UI." );
+
+class CLobbyPanel_Comp;
+
+#include "iclientmode.h"
+#include <vgui_controls/AnimationController.h>
+
+class CMatchHistoryEntryPanel : public CExpandablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CMatchHistoryEntryPanel, CExpandablePanel );
+ CMatchHistoryEntryPanel( Panel* pParent, const char *pszPanelname )
+ : BaseClass( pParent, pszPanelname )
+ {}
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/MatchHistoryEntryPanel.res" );
+ }
+
+ void SetMatchData( const CSOTFMatchResultPlayerStats& stats )
+ {
+ EditablePanel* pContainer = FindControl< EditablePanel >( "Container" );
+ if ( !pContainer )
+ return;
+
+ // Match date
+ CRTime matchdate( stats.endtime() );
+ char rtime_buf[k_RTimeRenderBufferSize];
+ matchdate.Render( rtime_buf );
+ pContainer->SetDialogVariable( "match_date", rtime_buf );
+
+ // Map name
+ const MapDef_t* pMapDef = GetItemSchema()->GetMasterMapDefByIndex( stats.map_index() );
+ const char* pszMapToken = "#TF_Map_Unknown";
+ if ( pMapDef )
+ {
+ pszMapToken = pMapDef->pszMapNameLocKey;
+ }
+ pContainer->SetDialogVariable( "map_name", g_pVGuiLocalize->Find( pszMapToken ) );
+
+ // KD ratio
+ float flKDRatio = stats.kills();
+ if ( stats.deaths() > 0 )
+ {
+ flKDRatio /= (float)stats.deaths();
+ }
+ pContainer->SetDialogVariable( "kd_ratio", CFmtStr( "%.1f", flKDRatio ) );
+
+ pContainer->SetControlVisible( "WinLabel", stats.display_rating_change() > 0 );
+ pContainer->SetControlVisible( "LossLabel", stats.display_rating_change() < 0 );
+
+ EditablePanel* pStatsContainer = FindControl< EditablePanel >( "SlidingStatsContainer", true );
+ if ( pStatsContainer )
+ {
+ pStatsContainer->SetDialogVariable( "stat_kills", LocalizeNumberWithToken( "TF_Competitive_Kills", stats.kills() ) );
+ pStatsContainer->SetDialogVariable( "stat_deaths", LocalizeNumberWithToken( "TF_Competitive_Deaths", stats.deaths() ) );
+ pStatsContainer->SetDialogVariable( "stat_damage", LocalizeNumberWithToken( "TF_Competitive_Damage", stats.damage() ) );
+ pStatsContainer->SetDialogVariable( "stat_healing", LocalizeNumberWithToken( "TF_Competitive_Healing", stats.healing() ) );
+ pStatsContainer->SetDialogVariable( "stat_support", LocalizeNumberWithToken( "TF_Competitive_Support", stats.support() ) );
+ pStatsContainer->SetDialogVariable( "stat_score", LocalizeNumberWithToken( "TF_Competitive_Score", stats.score() ) );
+
+ ScalableImagePanel* pMapImage = pStatsContainer->FindControl< ScalableImagePanel >( "BGImage", true );
+ if ( pMapImage && pMapDef )
+ {
+ char imagename[ 512 ];
+ Q_snprintf( imagename, sizeof( imagename ), "..\\vgui\\maps\\menu_thumb_%s", pMapDef->pszMapName );
+ pMapImage->SetImage( imagename );
+ }
+
+ // Lambdas, wherefore art thou...
+ SetupClassIcon( pStatsContainer, "scout", TF_CLASS_SCOUT, stats );
+ SetupClassIcon( pStatsContainer, "soldier", TF_CLASS_SOLDIER, stats );
+ SetupClassIcon( pStatsContainer, "pyro", TF_CLASS_PYRO, stats );
+ SetupClassIcon( pStatsContainer, "demo", TF_CLASS_DEMOMAN, stats );
+ SetupClassIcon( pStatsContainer, "heavy", TF_CLASS_HEAVYWEAPONS, stats );
+ SetupClassIcon( pStatsContainer, "engineer", TF_CLASS_ENGINEER, stats );
+ SetupClassIcon( pStatsContainer, "medic", TF_CLASS_MEDIC, stats );
+ SetupClassIcon( pStatsContainer, "sniper", TF_CLASS_SNIPER, stats );
+ SetupClassIcon( pStatsContainer, "spy", TF_CLASS_SPY, stats );
+
+ SetupMedalForStat( pStatsContainer, stats.kills_medal(), "kills" );
+ SetupMedalForStat( pStatsContainer, stats.damage_medal(), "damage" );
+ SetupMedalForStat( pStatsContainer, stats.healing_medal(), "healing" );
+ SetupMedalForStat( pStatsContainer, stats.support_medal(), "support" );
+ SetupMedalForStat( pStatsContainer, stats.score_medal(), "score" );
+ }
+ }
+private:
+
+ void SetupMedalForStat( EditablePanel* pParent, int nStat, const char* pszStatName )
+ {
+ ScalableImagePanel* pMedalImage = pParent->FindControl< ScalableImagePanel >( CFmtStr( "%sMedal", pszStatName ) );
+ if ( pMedalImage )
+ {
+ if ( nStat != StatMedal_None )
+ {
+ pMedalImage->SetImage( g_pszCompetitiveMedalImages[ nStat ] );
+ }
+ pMedalImage->SetVisible( nStat != StatMedal_None );
+ }
+ }
+
+ void SetupClassIcon( EditablePanel* pParent, const char* pszClassName, int nBit, const CSOTFMatchResultPlayerStats& stats )
+ {
+ ScalableImagePanel* pClassIcon = pParent->FindControl< ScalableImagePanel >( CFmtStr( "%sIcon", pszClassName ), true );
+ if( pClassIcon )
+ {
+ pClassIcon->SetImage( stats.classes_played() & (1<<nBit) ? CFmtStr( "class_icons/filter_%s_on", pszClassName ) : CFmtStr( "class_icons/filter_%s", pszClassName ) );
+ }
+ }
+};
+
+DECLARE_BUILD_FACTORY( CMatchHistoryEntryPanel );
+
+extern Color s_colorChallengeHeader;
+
+DECLARE_BUILD_FACTORY( CLadderLobbyLeaderboard );
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CLadderLobbyLeaderboard::CLadderLobbyLeaderboard( Panel *pParent, const char *pszPanelName )
+ : CTFLeaderboardPanel( pParent, pszPanelName )
+{
+ m_pScoreList = new vgui::EditablePanel( this, "ScoreList" );
+ m_pScoreListScroller = new vgui::ScrollableEditablePanel( this, m_pScoreList, "ScoreListScroller" );
+ m_pScoreListScroller->AddActionSignalTarget( this );
+
+ m_pszLeaderboardName = "tf2_ladder_6v6";
+
+ for ( int i = 0; i < 100; ++i )
+ {
+ vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( m_pScoreList, "LeaderboardEntry" );
+ m_vecLeaderboardEntries.AddToTail( pEntryUI );
+ }
+
+ m_pToolTip = new CTFTextToolTip( this );
+ m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" );
+ m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
+ m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
+ m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel );
+ m_pToolTip->SetTooltipDelay( 0 );
+
+ m_bIsDataValid = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLadderLobbyLeaderboard::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/econ/LobbyLeaderboard.res" );
+
+ FOR_EACH_VEC( m_vecLeaderboardEntries, i )
+ {
+ m_vecLeaderboardEntries[i]->ApplySchemeSettings( pScheme );
+ m_vecLeaderboardEntries[i]->LoadControlSettings( "Resource/UI/LeaderboardEntryRank.res" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLadderLobbyLeaderboard::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ UpdateLeaderboards();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLadderLobbyLeaderboard::OnCommand( const char *command )
+{
+ if ( Q_strnicmp( command, "stream", 6 ) == 0 )
+ {
+ vgui::system()->ShellExecute( "open", command + 7 );
+ return;
+ }
+ else if ( FStrEq( "global", command ) )
+ {
+ if ( m_bGlobal != true )
+ {
+ m_bGlobal = true;
+ UpdateLeaderboards();
+ }
+
+ return;
+ }
+ else if ( FStrEq( "local", command ) )
+ {
+ if ( m_bGlobal == true )
+ {
+ m_bGlobal = false;
+ UpdateLeaderboards();
+ }
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CLadderLobbyLeaderboard::GetLeaderboardData( CUtlVector< LeaderboardEntry_t* >& scores )
+{
+ CUtlVector< LeaderboardEntry_t* > vecLadderScores;
+ if ( Leaderboards_GetLadderLeaderboard( vecLadderScores, m_pszLeaderboardName, m_bGlobal ) )
+ {
+ scores.AddVectorToTail( vecLadderScores );
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CLadderLobbyLeaderboard::UpdateLeaderboards()
+{
+ CUtlVector< LeaderboardEntry_t* > scores;
+ m_bIsDataValid = GetLeaderboardData( scores );
+ if ( !m_bIsDataValid )
+ return false;
+
+ int nScoreListHeight = scores.Count() * m_yEntryStep;
+ int nScrollerWidth, nScrollerHeight;
+ m_pScoreListScroller->GetSize( nScrollerWidth, nScrollerHeight );
+
+ m_pScoreList->SetSize( nScrollerWidth, Max( nScoreListHeight, nScrollerHeight ) );
+
+ m_pScoreList->InvalidateLayout( true );
+ m_pScoreListScroller->InvalidateLayout( true );
+ m_pScoreListScroller->GetScrollbar()->InvalidateLayout( true );
+ static int nScrollWidth = m_pScoreListScroller->GetScrollbar()->GetWide();
+ m_pScoreListScroller->GetScrollbar()->SetWide( nScrollWidth>>1 );
+ if ( m_pScoreListScroller->GetScrollbar()->GetButton( 0 ) &&
+ m_pScoreListScroller->GetScrollbar()->GetButton( 1 ) )
+ {
+ m_pScoreListScroller->GetScrollbar()->GetButton( 0 )->SetVisible( false );
+ m_pScoreListScroller->GetScrollbar()->GetButton( 1 )->SetVisible( false );
+ }
+
+ FOR_EACH_VEC( m_vecLeaderboardEntries, i )
+ {
+ EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] );
+ if ( !pContainer )
+ continue;
+
+ Color colorToUse = i % 2 == 1 ? m_OddTextColor : m_EvenTextColor;
+
+ bool bIsEntryVisible = i < scores.Count();
+ pContainer->SetVisible( bIsEntryVisible );
+ pContainer->SetPos( 0, i * m_yEntryStep );
+ if ( bIsEntryVisible )
+ {
+ const LeaderboardEntry_t *pLeaderboardEntry = scores[i];
+ const CSteamID &steamID = pLeaderboardEntry->m_steamIDUser;
+
+#ifdef TWITCH_LEADERBOARD
+ TwitchTvAccountInfo_t *pTwitchInfo = StreamManager()->GetTwitchTvAccountInfo( steamID.ConvertToUint64() );
+ ETwitchTvState_t twitchState = pTwitchInfo ? pTwitchInfo->m_eTwitchTvState : k_ETwitchTvState_Error;
+ // still waiting for twitch info to load
+ if ( twitchState <= k_ETwitchTvState_Loading )
+ return false;
+#endif // TWITCH_LEADERBOARD
+
+ bool bIsLocalPlayer = steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUser()->GetSteamID() == steamID;
+ pContainer->SetDialogVariable( "position", m_bGlobal ? CFmtStr( "%d.", pLeaderboardEntry->m_nGlobalRank ) : "" );
+ pContainer->SetDialogVariable( "username", InventoryManager()->PersonaName_Get( steamID.GetAccountID() ) );
+
+ CExLabel *pNameText = dynamic_cast< CExLabel* >( pContainer->FindChildByName( "UserName" ) );
+ if ( pNameText )
+ {
+ pNameText->SetColorStr( bIsLocalPlayer ? m_LocalPlayerTextColor : colorToUse );
+ }
+
+ CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) );
+ if ( pAvatar )
+ {
+ pAvatar->SetShouldDrawFriendIcon( false );
+ pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 );
+ }
+
+ CTFBadgePanel *pRankImage = dynamic_cast< CTFBadgePanel* >( pContainer->FindChildByName( "RankImage" ) );
+ if ( pRankImage )
+ {
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( k_nMatchGroup_Ladder_6v6 );
+ const LevelInfo_t& levelInfo = pMatchDesc->m_pProgressionDesc->GetLevelForExperience( pLeaderboardEntry->m_nScore );
+ pRankImage->SetupBadge( pMatchDesc->m_pProgressionDesc, levelInfo );
+
+ wchar_t wszOutString[ 128 ];
+ char szLocalized[512];
+ wchar_t wszCount[ 16 ];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", levelInfo.m_nLevelNum );
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( pMatchDesc->m_pProgressionDesc->m_pszLevelToken );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, wszCount, g_pVGuiLocalize->Find( levelInfo.m_pszLevelTitle ) );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wszOutString, szLocalized, sizeof( szLocalized ) );
+
+ pRankImage->SetMouseInputEnabled( true );
+ pRankImage->SetVisible( true );
+ pRankImage->SetTooltip( m_pToolTip, szLocalized );
+ }
+
+ CExImageButton *pStreamImage = dynamic_cast< CExImageButton* >( pContainer->FindChildByName( "StreamImageButton" ) );
+ if ( pStreamImage )
+ {
+#ifdef TWITCH_LEADERBOARD
+ if ( twitchState == k_ETwitchTvState_Linked )
+ {
+ pStreamImage->SetVisible( true );
+ pStreamImage->SetCommand( CFmtStr( "stream %s", pTwitchInfo->m_sTwitchTvChannel.String() ) );
+ }
+ else
+#endif // TWITCH_LEADERBOARD
+ {
+ pStreamImage->SetVisible( false );
+ pStreamImage->SetCommand( "" );
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLadderLobbyLeaderboard::SetLeaderboard( const char *pszLeaderboardName, bool bGlobal )
+{
+ m_pszLeaderboardName = pszLeaderboardName;
+ m_bGlobal = bGlobal;
+
+ UpdateLeaderboards();
+}
+
+static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID )
+{
+ const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
+ V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CLobbyPanel_Comp::CLobbyPanel_Comp( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer )
+ : CBaseLobbyPanel( pParent, pLobbyContainer )
+ , m_pCompetitiveModeLeaderboard( NULL )
+ , m_pMatchHistoryScroller( NULL )
+ , m_eMatchSortMethod( SORT_BY_DATE )
+ , m_bDescendingMatchHistorySort( true )
+{
+ // Comp
+ m_fontMedalsCount = 0;
+
+ m_flCompetitiveRankProgress = -1.f;
+ m_flCompetitiveRankPrevProgress = -1.f;
+ m_flRefreshPlayerListTime = -1.f;
+ m_bCompetitiveRankChangePlayedSound = false;
+ m_bMatchHistoryLoaded = false;
+ m_bMatchDataForLocalPlayerDirty = true;
+
+ ListenForGameEvent( "gc_new_session" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CLobbyPanel_Comp::~CLobbyPanel_Comp()
+{
+ delete m_pImageList;
+ m_pImageList = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::OnCommand( const char *command )
+{
+
+ if ( FStrEq( command, "medals_help" ) )
+ {
+ CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( GetParent()->FindChildByName( "MedalsHelp" ) );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ return;
+ }
+ else if ( FStrEq( command, "show_leaderboards" ) )
+ {
+ if ( m_pCompetitiveModeLeaderboard )
+ {
+ m_pCompetitiveModeLeaderboard->SetVisible( true );
+ }
+
+ SetControlVisible( "MatchHistoryCategories", false, true );
+ SetControlVisible( "MatchHistoryContainer", false, true );
+
+ return;
+ }
+ else if ( FStrEq( command, "show_match_history" ) )
+ {
+ m_bMatchDataForLocalPlayerDirty = true;
+
+ if ( m_pCompetitiveModeLeaderboard )
+ {
+ m_pCompetitiveModeLeaderboard->SetVisible( false );
+ }
+
+ SetControlVisible( "MatchHistoryCategories", true, true );
+ SetControlVisible( "MatchHistoryContainer", true, true );
+
+ return;
+ }
+ else if ( !Q_strncmp( "sort", command, 4 ) )
+ {
+ EMatchHistorySortMethods_t eNewMethod = (EMatchHistorySortMethods_t)atoi( command + 4 );
+
+ if ( eNewMethod == m_eMatchSortMethod )
+ {
+ m_bDescendingMatchHistorySort = !m_bDescendingMatchHistorySort;
+ }
+ else
+ {
+ m_eMatchSortMethod = eNewMethod;
+ m_bDescendingMatchHistorySort = true;
+ }
+
+ m_bMatchDataForLocalPlayerDirty = true;
+
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CLobbyPanel_Comp::ShouldShowLateJoin() const
+{
+ return false; // For now
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::ApplyChatUserSettings( const CBaseLobbyPanel::LobbyPlayerInfo &player, KeyValues *pKV ) const
+{
+ pKV->SetInt( "has_ticket", 0 );
+ pKV->SetInt( "squad_surplus", 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::WriteGameSettingsControls()
+{
+ BaseClass::WriteGameSettingsControls();
+
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+
+ ++m_iWritingPanel;
+
+ m_pContainer->SetNextButtonEnabled( true );
+
+ SetControlVisible( "ScrollableContainer", GTFGCClientSystem()->GetWizardStep()== TF_Matchmaking_WizardStep_LADDER );
+
+ --m_iWritingPanel;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CLobbyPanel_Comp::GetMedalCountForStat( EMatchGroup unLadderType, RankStatType_t nStatType, int nMedalLevel )
+{
+ CSOTFLadderData *pData = GetLocalPlayerLadderData( unLadderType );
+ if ( !pData )
+ return 0;
+
+ switch ( nStatType )
+ {
+ case RankStat_Score:
+ if ( nMedalLevel == StatMedal_Bronze )
+ {
+ return pData->Obj().score_bronze();
+ }
+ else if ( nMedalLevel == StatMedal_Silver )
+ {
+ return pData->Obj().score_silver();
+ }
+ else if ( nMedalLevel == StatMedal_Gold )
+ {
+ return pData->Obj().score_gold();
+ }
+ break;
+ case RankStat_Kills:
+ if ( nMedalLevel == StatMedal_Bronze )
+ {
+ return pData->Obj().kills_bronze();
+ }
+ else if ( nMedalLevel == StatMedal_Silver )
+ {
+ return pData->Obj().kills_silver();
+ }
+ else if ( nMedalLevel == StatMedal_Gold )
+ {
+ return pData->Obj().kills_gold();
+ }
+ break;
+ case RankStat_Damage:
+ if ( nMedalLevel == StatMedal_Bronze )
+ {
+ return pData->Obj().damage_bronze();
+ }
+ else if ( nMedalLevel == StatMedal_Silver )
+ {
+ return pData->Obj().damage_silver();
+ }
+ else if ( nMedalLevel == StatMedal_Gold )
+ {
+ return pData->Obj().damage_gold();
+ }
+ break;
+ case RankStat_Healing:
+ if ( nMedalLevel == StatMedal_Bronze )
+ {
+ return pData->Obj().healing_bronze();
+ }
+ else if ( nMedalLevel == StatMedal_Silver )
+ {
+ return pData->Obj().healing_silver();
+ }
+ else if ( nMedalLevel == StatMedal_Gold )
+ {
+ return pData->Obj().healing_gold();
+ }
+ break;
+ case RankStat_Support:
+ if ( nMedalLevel == StatMedal_Bronze )
+ {
+ return pData->Obj().support_bronze();
+ }
+ else if ( nMedalLevel == StatMedal_Silver )
+ {
+ return pData->Obj().support_silver();
+ }
+ else if ( nMedalLevel == StatMedal_Gold )
+ {
+ return pData->Obj().support_gold();
+ }
+ break;
+ case RankStat_Deaths:
+ // Not supported
+ break;
+ default:
+ Assert( 0 );
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( m_bMatchDataForLocalPlayerDirty )
+ {
+ UpdateMatchDataForLocalPlayer();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::FireGameEvent( IGameEvent *event )
+{
+ const char *pszEventname = event->GetName();
+ if ( !Q_stricmp( pszEventname, "gc_new_session" ) )
+ {
+ // This is loaded on demand by the GC - if we have a new session, we need to re-request
+ if ( m_bMatchHistoryLoaded )
+ {
+ m_bMatchHistoryLoaded = false;
+ m_bMatchDataForLocalPlayerDirty = true;
+ }
+ }
+
+ BaseClass::FireGameEvent( event );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+EMatchGroup CLobbyPanel_Comp::GetMatchGroup( void ) const
+{
+ return k_nMatchGroup_Ladder_6v6;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
+{
+ if ( pObject->GetTypeID() != CSOTFMatchResultPlayerInfo::k_nTypeID )
+ return;
+
+ m_bMatchDataForLocalPlayerDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
+{
+ if ( pObject->GetTypeID() != CSOTFMatchResultPlayerInfo::k_nTypeID )
+ return;
+
+ m_bMatchDataForLocalPlayerDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ m_bMatchDataForLocalPlayerDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CLobbyPanel_Comp::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ EditablePanel* pPlaylistBGPanel = FindControl< EditablePanel >( "PlaylistBGPanel", true );
+ m_pCompetitiveModeLeaderboard = pPlaylistBGPanel->FindControl< CLadderLobbyLeaderboard >( "Leaderboard", true );
+
+ m_pMatchHistoryScroller = pPlaylistBGPanel->FindControl< CScrollableList >( "MatchHistoryContainer" );
+
+ int nAvatarWidth = ( ( m_iAvatarWidth * 5 / 4 ) + 1 );
+ int nExtraWidth = ( m_pChatPlayerList->GetWide() - ( 2 * nAvatarWidth ) - m_iPlayerNameWidth - m_iBannedWidth - m_iHasPassWidth );
+
+ m_pChatPlayerList->AddColumnToSection( 0, "avatar", "#TF_Players", vgui::SectionedListPanel::COLUMN_IMAGE, nAvatarWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "name", "", 0, m_iPlayerNameWidth + nExtraWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "is_banned", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iBannedWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "has_competitive_access", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iHasPassWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "rank", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, nAvatarWidth );
+ m_pChatPlayerList->SetDrawHeaders( false );
+ m_fontMedalsCount = pScheme->GetFont( "HudFontSmallestBold", true );
+}
+
+static int SortResult( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b )
+{
+ return a->display_rating_change() < b->display_rating_change();
+}
+
+static int SortDate( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b )
+{
+ return a->endtime() < b->endtime();
+}
+
+static int SortMap( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b )
+{
+ const MapDef_t* pMapDef = GetItemSchema()->GetMasterMapDefByIndex( a->map_index() );
+ const wchar_t* pszAMapName = g_pVGuiLocalize->Find( pMapDef ? pMapDef->pszMapNameLocKey : "#TF_Map_Unknown" );
+
+ pMapDef = GetItemSchema()->GetMasterMapDefByIndex( b->map_index() );
+ const wchar_t* pszBMapName = g_pVGuiLocalize->Find( pMapDef ? pMapDef->pszMapNameLocKey : "#TF_Map_Unknown" );
+
+ return wcscoll( pszAMapName, pszBMapName );
+}
+
+static int SortKDR( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b )
+{
+ float flAKDRatio = a->kills();
+ if ( a->deaths() > 0 )
+ {
+ flAKDRatio /= (float)a->deaths();
+ }
+
+ float flBKDRatio = b->kills();
+ if ( b->deaths() > 0 )
+ {
+ flBKDRatio /= (float)b->deaths();
+ }
+
+ return ( flBKDRatio - flAKDRatio ) * 1000.f;
+}
+
+void CLobbyPanel_Comp::UpdateMatchDataForLocalPlayer()
+{
+ m_bMatchDataForLocalPlayerDirty = false;
+
+ CUtlVector < CSOTFMatchResultPlayerStats > vecMatches;
+ GetLocalPlayerMatchHistory( GetMatchGroup(), vecMatches );
+
+ if ( !m_bMatchHistoryLoaded )
+ {
+ GCSDK::CProtoBufMsg< CMsgGCMatchHistoryLoad > msg( k_EMsgGCMatchHistoryLoad );
+ GCClientSystem()->BSendMessage( msg );
+
+ m_bMatchHistoryLoaded = true;
+ }
+
+ if ( m_pMatchHistoryScroller )
+ {
+ m_pMatchHistoryScroller->ClearAutoLayoutPanels();
+
+ typedef int (*MatchSortFunc)( const CSOTFMatchResultPlayerStats * a, const CSOTFMatchResultPlayerStats * b );
+
+ struct SortMethodData_t
+ {
+ MatchSortFunc m_pfnSort;
+ CExButton* m_pSortButton;
+ };
+
+ EditablePanel* pPlaylistBGPanel = FindControl< EditablePanel >( "PlaylistBGPanel", true );
+ Assert( pPlaylistBGPanel );
+ if ( !pPlaylistBGPanel )
+ return;
+
+ SortMethodData_t sortMethods[ NUM_SORT_METHODS ] = { { &SortResult, pPlaylistBGPanel->FindControl< CExButton >( "ResultButton", true ) }
+ , { &SortDate, pPlaylistBGPanel->FindControl< CExButton >( "DateButton", true ) }
+ , { &SortMap, pPlaylistBGPanel->FindControl< CExButton >( "MapButton", true ) }
+ , { &SortKDR, pPlaylistBGPanel->FindControl< CExButton >( "KDRButton", true ) } };
+
+ // Sort
+ vecMatches.Sort( sortMethods[ m_eMatchSortMethod ].m_pfnSort );
+
+ Label* pSortArrow = pPlaylistBGPanel->FindControl< Label >( "SortArrow", true );
+ Assert( pSortArrow );
+ if ( !pSortArrow )
+ return;
+
+ // Update controls
+ for( int i=0; i<ARRAYSIZE( sortMethods ); ++i )
+ {
+ if( sortMethods[ i ].m_pSortButton )
+ {
+ bool bSelected = i == m_eMatchSortMethod;
+ sortMethods[ i ].m_pSortButton->SetSelected( bSelected );
+
+ if ( bSelected )
+ {
+ int nDummy, nX;
+ sortMethods[ i ].m_pSortButton->GetContentSize( nX, nDummy );
+ // Move the sort arrow to the right edge of the selected panel
+ pSortArrow->SetPos( sortMethods[ i ].m_pSortButton->GetXPos() + nX , pSortArrow->GetYPos() );
+ // Fixup the label to be an up or down arrow
+ pSortArrow->SetText( m_bDescendingMatchHistorySort ? L"6" : L"5" );
+ }
+ }
+ }
+
+ // Potentially go backwards
+ if ( m_bDescendingMatchHistorySort )
+ {
+ FOR_EACH_VEC( vecMatches, i )
+ {
+ CMatchHistoryEntryPanel* pMatchEntryPanel = new CMatchHistoryEntryPanel( m_pMatchHistoryScroller, "MatchEntry" );
+ pMatchEntryPanel->MakeReadyForUse();
+ pMatchEntryPanel->SetMatchData( vecMatches[ i ] );
+ m_pMatchHistoryScroller->AddPanel( pMatchEntryPanel, 5 );
+ }
+ }
+ else
+ {
+ FOR_EACH_VEC_BACK( vecMatches, i )
+ {
+ CMatchHistoryEntryPanel* pMatchEntryPanel = new CMatchHistoryEntryPanel( m_pMatchHistoryScroller, "MatchEntry" );
+ pMatchEntryPanel->MakeReadyForUse();
+ pMatchEntryPanel->SetMatchData( vecMatches[ i ] );
+ m_pMatchHistoryScroller->AddPanel( pMatchEntryPanel, 5 );
+ }
+ }
+
+ m_pMatchHistoryScroller->MakeReadyForUse();
+ }
+}
diff --git a/game/client/tf/vgui/tf_lobbypanel_comp.h b/game/client/tf/vgui/tf_lobbypanel_comp.h
new file mode 100644
index 0000000..1e0d843
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel_comp.h
@@ -0,0 +1,139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#ifndef TF_LOBBYPANEL_COMP_H
+#define TF_LOBBYPANEL_COMP_H
+
+#include "cbase.h"
+#include "game/client/iviewport.h"
+#include "tf_lobbypanel.h"
+#include "tf_leaderboardpanel.h"
+#include "local_steam_shared_object_listener.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+using namespace GCSDK;
+
+class CBaseLobbyPanel;
+
+namespace vgui
+{
+ class ScrollableEditablePanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CLadderLobbyLeaderboard : public CTFLeaderboardPanel
+{
+ DECLARE_CLASS_SIMPLE( CLadderLobbyLeaderboard, CTFLeaderboardPanel );
+public:
+
+ CLadderLobbyLeaderboard( Panel *pParent, const char *pszPanelName );
+
+ //-----------------------------------------------------------------------------
+ // Purpose: Create leaderboard panels
+ //-----------------------------------------------------------------------------
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ virtual bool GetLeaderboardData( CUtlVector< LeaderboardEntry_t* >& scores );
+ virtual bool UpdateLeaderboards();
+
+ void SetLeaderboard( const char *pszLeaderboardName, bool bGlobal );
+
+ const char *GetLeaderboardName() const { return m_pszLeaderboardName; }
+ bool IsDataValid( void ) { return m_bIsDataValid; }
+
+private:
+ const char *m_pszLeaderboardName;
+ bool m_bGlobal;
+ bool m_bIsDataValid;
+
+ vgui::ScrollableEditablePanel *m_pScoreListScroller;
+ EditablePanel *m_pScoreList;
+
+ CTFTextToolTip *m_pToolTip;
+ vgui::EditablePanel *m_pToolTipEmbeddedPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CLobbyPanel_Comp : public CBaseLobbyPanel, public CLocalSteamSharedObjectListener
+{
+ DECLARE_CLASS_SIMPLE( CLobbyPanel_Comp, CBaseLobbyPanel );
+
+public:
+ CLobbyPanel_Comp( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer );
+ virtual ~CLobbyPanel_Comp();
+
+ //
+ // Panel overrides
+ //
+ virtual void PerformLayout() OVERRIDE;
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ virtual EMatchGroup GetMatchGroup( void ) const OVERRIDE;
+
+ virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE;
+ virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE;
+
+ virtual void OnThink() OVERRIDE;
+
+ //
+ // CGameEventListener overrides
+ //
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+private:
+ virtual bool ShouldShowLateJoin() const OVERRIDE;
+ virtual void ApplyChatUserSettings( const LobbyPlayerInfo &player,KeyValues *pKV ) const OVERRIDE;
+ virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyPanel_Comp.res"; }
+
+ CPanelAnimationVarAliasType( int, m_iStatMedalWidth, "stat_medal_width", "14", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iMedalCountWidth, "stat_medal_count_width", "20", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iHasPassWidth, "has_pass_width", "12", "proportional_int" );
+
+ CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels;
+
+ // leaderboards
+ CLadderLobbyLeaderboard *m_pCompetitiveModeLeaderboard;
+
+ vgui::HFont m_fontMedalsCount;
+
+ enum EMatchHistorySortMethods_t
+ {
+ SORT_BY_RESULT = 0,
+ SORT_BY_DATE,
+ SORT_BY_MAP,
+ SORT_BY_KDR,
+
+ NUM_SORT_METHODS
+ };
+
+ CScrollableList* m_pMatchHistoryScroller;
+ EMatchHistorySortMethods_t m_eMatchSortMethod;
+ bool m_bDescendingMatchHistorySort;
+
+ float m_flCompetitiveRankProgress;
+ float m_flCompetitiveRankPrevProgress;
+ float m_flRefreshPlayerListTime;
+ bool m_bCompetitiveRankChangePlayedSound;
+ bool m_bMatchHistoryLoaded;
+
+ void WriteGameSettingsControls() OVERRIDE;
+
+ int GetMedalCountForStat( EMatchGroup unLadderType, RankStatType_t nStatType, int nMedalLevel );
+
+
+ void UpdateMatchDataForLocalPlayer();
+ bool m_bMatchDataForLocalPlayerDirty;
+};
+
+#endif //TF_LOBBYPANEL_COMP_H
diff --git a/game/client/tf/vgui/tf_lobbypanel_mvm.cpp b/game/client/tf/vgui/tf_lobbypanel_mvm.cpp
new file mode 100644
index 0000000..2132da3
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel_mvm.cpp
@@ -0,0 +1,1114 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+
+#include "vgui_controls/PropertySheet.h"
+#include "vgui_controls/SectionedListPanel.h"
+#include "vgui_bitmapimage.h"
+#include "vgui_avatarimage.h"
+#include "store/store_panel.h"
+#include <VGuiMatSurface/IMatSystemSurface.h>
+#include <vgui_controls/ImageList.h>
+
+#include "tf_lobbypanel_mvm.h"
+#include "tf_lobby_container_frame_mvm.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+extern ConVar tf_matchmaking_join_in_progress;
+ConVar tf_matchmaking_ticket_help( "tf_matchmaking_ticket_help", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "Saved if the player has see the ticket help screen." );
+
+const int k_iPopIndex_Any = -1000;
+const int k_iPopIndex_OnlyNotYetCompleted = -1001;
+const int k_iPopIndex_AnyNormal = -1002;
+const int k_iPopIndex_AnyIntermediate = -1003;
+const int k_iPopIndex_AnyAdvanced = -1004;
+const int k_iPopIndex_AnyExpert = -1005;
+const int k_iPopIndex_AnyHaunted = -1006;
+
+static void GetMvmChallengeSet( int idxChallenge, CMvMMissionSet &result )
+{
+ result.Clear();
+
+ if ( idxChallenge >= 0 )
+ {
+ result.SetMissionBySchemaIndex( idxChallenge, true );
+ return;
+ }
+
+ bool bMannUP = GTFGCClientSystem()->GetSearchPlayForBraggingRights();
+#ifdef USE_MVM_TOUR
+ int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex();
+ Assert( bMannUP || idxTour < 0 );
+#endif // USE_MVM_TOUR
+
+#ifdef USE_MVM_TOUR
+ uint32 nNotCompletedChallenges = ~0U;
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty )
+ {
+ for ( int i = 0 ; i < pParty->GetNumMembers() ; ++i )
+ {
+ nNotCompletedChallenges &= ~pParty->Obj().members( i ).completed_missions();
+ }
+ }
+ else
+ {
+ if ( idxTour >= 0 )
+ {
+ uint32 nTours = 0, nCompletedChallenge = 0;
+ GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge );
+
+ nNotCompletedChallenges = ~nCompletedChallenge;
+ }
+ }
+#endif // USE_MVM_TOUR
+
+ for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
+ {
+ const MvMMission_t &chal = GetItemSchema()->GetMvmMissions()[ i ];
+
+ // Cannot select non-MannUp missions in mann up mode
+#ifdef USE_MVM_TOUR
+ int iBadgeSlot = (idxTour < 0) ? -1 : GetItemSchema()->GetMvmMissionBadgeSlotForTour( idxTour, i );
+ if ( bMannUP && iBadgeSlot < 0 )
+ continue;
+#else // new mm
+ bool bIsChallengeInMannUp = chal.m_unMannUpPoints > 0;
+ if ( bMannUP && !bIsChallengeInMannUp )
+ continue;
+#endif // USE_MVM_TOUR
+
+ // Does this challenge fit the search criteria?
+ bool bSelect = false;
+ switch ( idxChallenge )
+ {
+ case k_iPopIndex_Any:
+ bSelect = true;
+ break;
+ case k_iPopIndex_OnlyNotYetCompleted:
+#ifdef USE_MVM_TOUR
+ if ( iBadgeSlot >= 0 )
+ {
+ int iChallengeBit = ( 1 << iBadgeSlot );
+ if ( nNotCompletedChallenges & iChallengeBit )
+ {
+ bSelect = true;
+ }
+ }
+#endif // USE_MVM_TOUR
+ break;
+
+ case k_iPopIndex_AnyNormal:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Normal );
+ break;
+
+ case k_iPopIndex_AnyIntermediate:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Intermediate );
+ break;
+
+ case k_iPopIndex_AnyAdvanced:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Advanced );
+ break;
+
+ case k_iPopIndex_AnyExpert:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Expert );
+ break;
+
+ case k_iPopIndex_AnyHaunted:
+ bSelect = ( chal.m_eDifficulty == k_EMvMChallengeDifficulty_Haunted );
+ break;
+
+ default:
+ Assert( false );
+ }
+ result.SetMissionBySchemaIndex( i, bSelect );
+ }
+}
+
+extern Color s_colorBannedPlayerListItem;
+extern Color s_colorPlayerListItem;
+extern Color s_colorChatRemovedFromQueue;
+extern Color s_colorChatAddedToQueue;
+extern Color s_colorChatPlayerJoinedParty;
+extern Color s_colorChatPlayerJoinedPartyName;
+extern Color s_colorChatPlayerLeftParty;
+extern Color s_colorChatPlayerLeftPartyName;
+extern Color s_colorChatPlayerChatName;
+extern Color s_colorChatPlayerChatText;
+extern Color s_colorChatDefault;
+extern Color s_colorChallengeForegroundEnabled;
+extern Color s_colorChallengeForegroundHaunted;
+extern Color s_colorChallengeForegroundDisabled;
+extern Color s_colorChallengeHeader;
+
+
+static void GetPlayerNameForSteamID( wchar_t *wCharPlayerName, int nBufSizeBytes, const CSteamID &steamID )
+{
+ const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
+ V_UTF8ToUnicode( pszName, wCharPlayerName, nBufSizeBytes );
+}
+
+CLobbyPanel_MvM::CLobbyPanel_MvM( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer )
+ : CBaseLobbyPanel( pParent, pLobbyContainer )
+{
+ m_pMvMMannVsMachineGroupPanel = NULL;
+ m_pMvMMannUpGroupPanel = NULL;
+ m_pMvMPracticeGroupPanel = NULL;
+ m_pMvMTourOfDutyGroupPanel = NULL;
+ m_pMvMTourOfDutyListGroupBox = NULL;
+ m_pTourList = NULL;
+
+ m_MvMEconItemsGroupBox = NULL;
+ m_pSquadSurplusCheckButton = NULL;
+ m_pOpenStoreButton = NULL;
+ m_pOpenStoreButton2 = NULL;
+ m_pOpenHelpButton = NULL;
+ m_pMannUpNowButton = NULL;
+ m_pMannUpTourLootDescriptionBox = NULL;
+ m_pMannUpTourLootImage = NULL;
+ //m_pMannUpTourLootDetailLabel = NULL;
+ m_pTourDifficultyWarning = NULL;
+
+ m_MvMPracticeGroupPanel = NULL;
+
+ m_pMvMSelectChallengeGroupPanel = NULL;
+ m_pMVMChallengeListGroupBox = NULL;
+
+ // MvM
+ m_pMvMMannVsMachineGroupPanel = new vgui::EditablePanel( this, "MannVsMachineGroupBox" );
+ m_pMvMMannUpGroupPanel = new vgui::EditablePanel( this, "MannUpGroupBox" );
+ m_pMvMPracticeGroupPanel = new vgui::EditablePanel( this, "PracticeGroupBox" );
+ m_pMvMTourOfDutyGroupPanel = new vgui::EditablePanel( this, "MvMTourOfDutyGroupBox" );
+
+ m_MvMEconItemsGroupBox = new vgui::EditablePanel( this, "MvMEconItemsGroupBox" );
+ m_pMannUpTicketImage = new vgui::ImagePanel( m_MvMEconItemsGroupBox, "MannUpTicketImage" );
+ m_pSquadSurplusImage = new vgui::ImagePanel( m_MvMEconItemsGroupBox, "SquadSurplusImage" );
+
+ m_pMannUpTourLootDescriptionBox = new vgui::EditablePanel( this, "MannUpTourLootDescriptionBox" );
+ m_pMannUpTourLootImage = new vgui::ImagePanel( m_pMannUpTourLootDescriptionBox, "TourLootImage" );
+ //m_pMannUpTourLootDetailLabel = new vgui::Label( m_pMannUpTourLootDescriptionBox, "TourLootDetailLabel", " );
+
+ m_MvMPracticeGroupPanel = new vgui::EditablePanel( this, "MvMPracticeGroupBox" );
+
+ m_pMvMSelectChallengeGroupPanel = new vgui::EditablePanel( this, "MvMSelectChallengeGroupBox" );
+ m_pMVMChallengeListGroupBox = new vgui::EditablePanel( m_pMvMSelectChallengeGroupPanel, "ChallengeListGroupBox" );
+ m_pChallengeList = new ChallengeList( this, m_pMVMChallengeListGroupBox, "ChallengeList" );
+
+ m_pMvMTourOfDutyListGroupBox = new vgui::EditablePanel( m_pMvMTourOfDutyGroupPanel, "TourlistGroupBox" );
+ m_pTourList = new vgui::SectionedListPanel( m_pMvMTourOfDutyListGroupBox, "TourList" );
+
+ m_pTourDifficultyWarning = new vgui::Label( m_pMvMTourOfDutyGroupPanel, "TourDifficultyWarning", "" );
+
+ m_fontChallengeListHeader = 0;
+ m_fontChallengeListItem = 0;
+}
+
+CLobbyPanel_MvM::~CLobbyPanel_MvM()
+{}
+
+//-----------------------------------------------------------------------------
+void CLobbyPanel_MvM::FireGameEvent( IGameEvent *event )
+{
+ BaseClass::FireGameEvent( event );
+
+ const char *pszEventName = event->GetName();
+
+ if ( !Q_stricmp( pszEventName, "mm_lobby_member_join" ) )
+ {
+#ifdef USE_MVM_TOUR
+ WriteTourList();
+#endif // USE_MVM_TOUR
+ WriteChallengeList();
+
+ return;
+ }
+ else if ( !Q_stricmp( pszEventName, "mm_lobby_member_leave" ) )
+ {
+#ifdef USE_MVM_TOUR
+ WriteTourList();
+#endif // USE_MVM_TOUR
+ WriteChallengeList();
+
+ return;
+ }
+}
+
+void CLobbyPanel_MvM::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "open_store_ticket" ) )
+ {
+ // Open the store, and show the upgrade advice
+ EconUI()->CloseEconUI();
+
+ CSchemaItemDefHandle hItemDef( CTFItemSchema::k_rchMvMTicketItemDefName );
+ EconUI()->GetStorePanel()->AddToCartAndCheckoutImmediately( hItemDef->GetDefinitionIndex() );
+ return;
+ }
+ else if ( FStrEq( command, "open_store_voucher" ) )
+ {
+ // Open the store, and show the upgrade advice
+ EconUI()->CloseEconUI();
+
+ CSchemaItemDefHandle hItemDef( CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName );
+ EconUI()->GetStorePanel()->AddToCartAndCheckoutImmediately( hItemDef->GetDefinitionIndex() );
+ return;
+ }
+ else if ( FStrEq( command, "open_help" ) )
+ {
+ CExplanationPopup *pPopup = dynamic_cast< CExplanationPopup* >( GetParent()->FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ return;
+ }
+ else if ( FStrEq( command, "mann_up_now" ) )
+ {
+ GTFGCClientSystem()->SetSearchChallenges( CMvMMissionSet() );
+ m_pContainer->OnCommand( "back" );
+ m_pContainer->OnCommand( "mannup" );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CLobbyPanel_MvM::SetMannUpTicketCount( int nCount )
+{
+ m_pMannUpTicketImage->SetImage( nCount > 0 ? "pve/mvm_ticket_active" : "pve/mvm_ticket_inactive" );
+
+ char szCount[ 5 ];
+ V_snprintf( szCount, sizeof( szCount ), "%i", nCount );
+
+ m_MvMEconItemsGroupBox->SetDialogVariable( "ticket_count", szCount );
+}
+
+void CLobbyPanel_MvM::SetSquadSurplusCount( int nCount )
+{
+ char szCount[ 5 ];
+ V_snprintf( szCount, sizeof( szCount ), "%i", nCount );
+
+ m_MvMEconItemsGroupBox->SetDialogVariable( "voucher_count", szCount );
+}
+
+EMatchGroup CLobbyPanel_MvM::GetMatchGroup( void ) const
+{
+ return GTFGCClientSystem()->GetSearchPlayForBraggingRights() ? k_nMatchGroup_MvM_MannUp : k_nMatchGroup_MvM_Practice;
+}
+
+void CLobbyPanel_MvM::OnCheckButtonChecked( vgui::Panel *panel )
+{
+ if ( m_iWritingPanel > 0 )
+ return;
+
+ if ( panel == m_pSquadSurplusCheckButton )
+ {
+ if ( BIsPartyInUIState() && GTFGCClientSystem()->GetSearchPlayForBraggingRights() )
+ {
+ if ( m_pSquadSurplusCheckButton->IsSelected() )
+ {
+ if ( GTFGCClientSystem()->BLocalPlayerInventoryHasSquadSurplusVoucher() )
+ {
+ GTFGCClientSystem()->SetLocalPlayerSquadSurplus( true );
+ m_pSquadSurplusImage->SetImage( "pve/mvm_voucher_active" );
+ }
+ else
+ {
+ m_pSquadSurplusCheckButton->SetSilentMode( true );
+ m_pSquadSurplusCheckButton->SetSelected( false );
+ m_pSquadSurplusCheckButton->SetSilentMode( false );
+ ShowEconRequirementDialog( "#TF_MvM_RequiresSquadSurplusVoucher_Title", "#TF_MvM_RequiresSquadSurplusVoucher", CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName );
+ }
+ }
+ else
+ {
+ GTFGCClientSystem()->SetLocalPlayerSquadSurplus( false );
+
+ m_pSquadSurplusImage->SetImage( "pve/mvm_voucher_inactive" );
+ }
+ }
+ else
+ {
+ WriteGameSettingsControls();
+ }
+
+ return;
+ }
+
+ BaseClass::OnCheckButtonChecked( panel );
+}
+
+
+void CLobbyPanel_MvM::OnItemLeftClick( vgui::Panel* panel )
+{
+ if ( m_iWritingPanel > 0 )
+ return;
+
+#ifdef USE_MVM_TOUR
+ if ( panel == m_pTourList )
+ {
+ OnClickedOnTour();
+ }
+#endif // USE_MVM_TOUR
+ else if ( panel == m_pChallengeList )
+ {
+ OnClickedOnChallenge();
+ }
+
+ BaseClass::OnItemLeftClick( panel );
+}
+
+void CLobbyPanel_MvM::ApplyChatUserSettings( const LobbyPlayerInfo& player, KeyValues* pSettings ) const
+{
+ if ( GTFGCClientSystem()->GetSearchPlayForBraggingRights() )
+ {
+ pSettings->SetInt( "has_ticket", player.m_bHasTicket ? m_iImageHasTicket : m_iImageNoTicket );
+ pSettings->SetInt( "squad_surplus", player.m_bSquadSurplus ? m_iImageSquadSurplus : m_iImageNoSquadSurplus );
+ }
+}
+
+#ifdef USE_MVM_TOUR
+void CLobbyPanel_MvM::OnClickedOnTour()
+{
+ int iSelected = m_pTourList->GetSelectedItem();
+ m_pTourList->SetSelectedItem( -1 );
+ if ( iSelected < 0 )
+ return;
+ if ( BIsPartyLeader() && BIsPartyInUIState() )
+ {
+ int iTourIndex = m_pTourList->GetItemData( iSelected )->GetInt( "tour_index", -1 );
+ Assert( iTourIndex >= 0 );
+ GTFGCClientSystem()->SetSearchMannUpTourIndex( iTourIndex );
+ }
+ else
+ {
+ WriteChallengeList();
+ }
+}
+#endif // USE_MVM_TOUR
+
+void CLobbyPanel_MvM::WriteGameSettingsControls()
+{
+ BaseClass::WriteGameSettingsControls();
+
+ // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.)
+ // We might get an event or something right at the transition point occasionally when the UI should
+ // not be visible
+ if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive )
+ {
+ return;
+ }
+
+ ++m_iWritingPanel;
+
+ bool bLeader = BIsPartyLeader();
+ bool bInUIState = BIsPartyInUIState();
+
+ TF_Matchmaking_WizardStep eWizardStep = GTFGCClientSystem()->GetWizardStep();
+
+ // MVM
+ m_pMvMMannVsMachineGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS );
+ m_pMvMMannUpGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS );
+ m_pMvMPracticeGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS );
+ m_pMannUpTourLootDescriptionBox->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY );
+ m_pMvMTourOfDutyGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY );
+ m_pMvMSelectChallengeGroupPanel->SetVisible( eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+
+ bool bShowBottomPanel = ( eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE );
+
+ if ( !tf_matchmaking_ticket_help.GetBool() && bShowBottomPanel && GTFGCClientSystem()->GetSearchPlayForBraggingRights() )
+ {
+ OnCommand( "open_help" );
+ tf_matchmaking_ticket_help.SetValue( 1 );
+ }
+
+ m_MvMEconItemsGroupBox->SetVisible( bShowBottomPanel && GTFGCClientSystem()->GetSearchPlayForBraggingRights() );
+ m_MvMPracticeGroupPanel->SetVisible( bShowBottomPanel && !GTFGCClientSystem()->GetSearchPlayForBraggingRights() );
+
+ if ( m_pMvMTourOfDutyGroupPanel->IsVisible() )
+ {
+ SetNavToRelay( m_pMvMTourOfDutyGroupPanel->GetName() );
+ }
+ else if ( m_pMvMSelectChallengeGroupPanel->IsVisible() )
+ if ( m_pMvMSelectChallengeGroupPanel->IsVisible() )
+ {
+ SetNavToRelay( m_pMvMSelectChallengeGroupPanel->GetName() );
+ }
+
+ if ( m_MvMPracticeGroupPanel->IsVisible() )
+ {
+ SetNavToRelay( m_MvMPracticeGroupPanel->GetName() );
+ }
+ else if ( m_MvMEconItemsGroupBox->IsVisible() )
+ {
+ SetNavToRelay( m_MvMEconItemsGroupBox->GetName() );
+ }
+
+ m_pContainer->SetNextButtonEnabled( true );
+
+
+#ifdef USE_MVM_TOUR
+ WriteTourList();
+#endif // USE_MVM_TOUR
+ WriteChallengeList();
+
+ FOR_EACH_VEC( m_vecSearchCriteriaLabels, i )
+ {
+ m_vecSearchCriteriaLabels[i]->SetEnabled( bInUIState );
+ }
+
+ m_pMVMChallengeListGroupBox->SetControlVisible( "GreyOutPanel", !( bLeader && bInUIState ) );
+#ifdef USE_MVM_TOUR
+ m_pMvMTourOfDutyListGroupBox->SetControlVisible( "GreyOutPanel", !( bLeader && bInUIState ) );
+#endif // USE_MVM_TOUR
+ bool bPlayForBraggingRights = GTFGCClientSystem()->GetSearchPlayForBraggingRights();
+ m_pSquadSurplusCheckButton->SetEnabled( bInUIState && bPlayForBraggingRights );
+ m_pSquadSurplusCheckButton->SetSilentMode( true );
+ m_pSquadSurplusCheckButton->SetSelected( GTFGCClientSystem()->GetLocalPlayerSquadSurplus() );
+ m_pSquadSurplusCheckButton->SetSilentMode( false );
+
+ m_pSquadSurplusImage->SetImage( GTFGCClientSystem()->GetLocalPlayerSquadSurplus() ? "pve/mvm_voucher_active" : "pve/mvm_voucher_inactive" );
+
+ --m_iWritingPanel;
+}
+
+bool CLobbyPanel_MvM::ShouldShowLateJoin() const
+{
+ TF_Matchmaking_WizardStep eWizardStep = GTFGCClientSystem()->GetWizardStep();
+ return
+#ifdef USE_MVM_TOUR
+ ( eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY ) ||
+#endif // USE_MVM_TOUR
+ ( eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE ) ||
+ ( ( eWizardStep == TF_Matchmaking_WizardStep_SEARCHING ) && ( GTFGCClientSystem()->GetSearchMode() == TF_Matchmaking_MVM ) );
+}
+
+#ifdef USE_MVM_TOUR
+void CLobbyPanel_MvM::WriteTourList()
+{
+ if ( !GTFGCClientSystem()->GetSearchPlayForBraggingRights() )
+ return;
+
+ ++m_iWritingPanel;
+
+ bool bLeader = BIsPartyLeader();
+ bool bInUIState = BIsPartyInUIState();
+ int idxSelectedTour = GTFGCClientSystem()->GetSearchMannUpTourIndex();
+
+ m_pTourList->RemoveAll();
+ m_pTourList->RemoveAllSections();
+
+ m_pTourList->SetClickable( bLeader && bInUIState );
+ m_pTourList->AddSection( 0, "Tour name" );
+ m_pTourList->SetSectionAlwaysVisible( 0, false );
+ m_pTourList->SetSectionFgColor( 0, s_colorChallengeHeader );
+ m_pTourList->SetSectionDividerColor( 0, Color(0,0,0,0) );
+ m_pTourList->AddColumnToSection( 0, "new","", vgui::SectionedListPanel::COLUMN_IMAGE, m_iNewWidth );
+ m_pTourList->AddColumnToSection( 0, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth );
+ m_pTourList->AddColumnToSection( 0, "spacer", "", 0, m_iChallengeSpacer );
+ m_pTourList->AddColumnToSection( 0, "display_name", "Tour", 0, m_iTourNameWidth );
+ m_pTourList->AddColumnToSection( 0, "skill", "Difficulty", 0, m_iTourSkillWidth );
+ m_pTourList->AddColumnToSection( 0, "progress", "Progress", 0, m_iTourProgressWidth );
+ m_pTourList->AddColumnToSection( 0, "badge_level", "Tours Completed", 0, m_iTourNumberWidth );
+ m_pTourList->SetFontSection( 0, m_fontChallengeListHeader );
+
+ bool bCompletedOneAdvancedTour = false;
+ uint32 unBadgeLevel = 0, unCompletedChallengeMask = 0;
+ const char *pszWarningString = "#TF_MVM_Tour_ExpertDifficulty_Warning";
+
+ // Local player has completed at least one Advanced tour?
+ FOR_EACH_VEC( GetItemSchema()->GetMvmTours(), idxTour )
+ {
+ GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &unBadgeLevel, &unCompletedChallengeMask );
+
+ const MvMTour_t &tourInfo = GetItemSchema()->GetMvmTours()[idxTour];
+ if ( tourInfo.m_eDifficulty >= k_EMvMChallengeDifficulty_Advanced && unBadgeLevel > 0 )
+ {
+ bCompletedOneAdvancedTour = true;
+ break;
+ }
+ }
+
+ // Add a row for each tour
+ FOR_EACH_VEC( GetItemSchema()->GetMvmTours(), idxTour )
+ {
+ const MvMTour_t &tour = GetItemSchema()->GetMvmTours()[ idxTour ];
+
+ KeyValues *kvItem = new KeyValues("item");
+
+ GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &unBadgeLevel, &unCompletedChallengeMask );
+
+ int nCompletedChallengeCount = 0;
+ for ( int i = 0 ; i < tour.m_vecMissions.Count() ; ++i )
+ {
+ if ( unCompletedChallengeMask & ( 1 << tour.m_vecMissions[i].m_iBadgeSlot ) )
+ {
+ ++nCompletedChallengeCount;
+ }
+ }
+
+ char cchTemp[256];
+ V_sprintf_safe( cchTemp, "%d / %d", nCompletedChallengeCount, tour.m_vecMissions.Count() );
+ kvItem->SetString( "progress", cchTemp );
+
+ uint32 iTourNumber = Max( 1U, unBadgeLevel );
+ V_sprintf_safe( cchTemp, "%d", iTourNumber );
+ kvItem->SetString( "badge_level", cchTemp );
+
+ if ( tour.m_bIsNew )
+ {
+ kvItem->SetInt( "new", m_iImageNew );
+ }
+
+ kvItem->SetInt( "check_box", idxSelectedTour == idxTour ? m_iImageRadioButtonYes : m_iImageRadioButtonNo );
+ kvItem->SetString( "display_name", tour.m_sTourNameLocalizationToken.Get() );
+ kvItem->SetString( "skill", GetMvMChallengeDifficultyLocName( tour.m_eDifficulty ) );
+ kvItem->SetInt( "tour_index", idxTour );
+ int itemID = m_pTourList->AddItem( 0, kvItem );
+ m_pTourList->SetItemFont( itemID, m_fontChallengeListItem );
+
+ if ( tour.m_eDifficulty >= k_EMvMChallengeDifficulty_Expert && !bCompletedOneAdvancedTour )
+ {
+ m_pTourList->SetItemFgColor( itemID, s_colorBannedPlayerListItem );
+ pszWarningString = "#TF_MVM_Tour_ExpertDifficulty_Denied";
+ }
+ else
+ {
+ m_pTourList->SetItemFgColor( itemID, s_colorChallengeForegroundEnabled );
+ }
+ }
+ m_pTourList->SetSelectedItem( idxSelectedTour );
+
+ const char *pszSelectedTourLocToken = "TF_MvM_Tour_NoSelection";
+ const char *pszLootImage = "pve/mvm_loot_image";
+ bool bShowDifficultyWarning = false;
+ if ( idxSelectedTour >= 0 )
+ {
+ const MvMTour_t &tour = GetItemSchema()->GetMvmTours()[ idxSelectedTour ];
+ pszLootImage = tour.m_sLootImageName.Get();
+ pszSelectedTourLocToken = tour.m_sTourNameLocalizationToken.Get();
+
+ // Check if we should show the difficulty warning
+ if ( tour.m_eDifficulty >= k_EMvMChallengeDifficulty_Expert )
+ {
+ // Deny expert mode if they haven't completed at least one Advanced tour
+ if ( !bCompletedOneAdvancedTour )
+ {
+ m_pContainer->SetNextButtonEnabled( false );
+ }
+
+ // Local player hasn't completed one mission?
+ if ( !GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxSelectedTour, &unBadgeLevel, &unCompletedChallengeMask )
+ || unBadgeLevel == 0 )
+ {
+ bShowDifficultyWarning = true;
+ }
+
+ // Anybody in the party hasn't completed a mission?
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( pParty != NULL )
+ {
+ for ( int i = 0 ; !bShowDifficultyWarning && i < pParty->GetNumMembers() ; ++i )
+ {
+ if ( pParty->Obj().members( i ).badge_level() == 0 )
+ {
+ bShowDifficultyWarning = true;
+ }
+ }
+ }
+ }
+ }
+
+ char archTemp[ 256 ];
+ V_sprintf_safe( archTemp, "%s_LootDescription", pszSelectedTourLocToken );
+ m_pMannUpTourLootDescriptionBox->SetDialogVariable( "tour_loot_detail", g_pVGuiLocalize->Find( archTemp ) );
+
+ m_pMannUpTourLootImage->SetImage( pszLootImage );
+
+ wchar_t wszLocalized[512];
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( pszWarningString ), 0 );
+ m_pTourDifficultyWarning->SetText( wszLocalized );
+ m_pTourDifficultyWarning->SetVisible( bShowDifficultyWarning );
+
+ --m_iWritingPanel;
+}
+#endif // USE_MVM_TOUR
+
+void CLobbyPanel_MvM::WriteChallengeList()
+{
+ ++m_iWritingPanel;
+
+ bool bLeader = BIsPartyLeader();
+ bool bInUIState = BIsPartyInUIState();
+ bool bForBraggingRights = GTFGCClientSystem()->GetSearchPlayForBraggingRights();
+
+#ifdef USE_MVM_TOUR
+ int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex();
+
+ char szTours[ 8 ] = "";
+ if ( idxTour >= 0 )
+ {
+ uint32 nTours, nCompletedChallenge;
+ GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge );
+ if ( nTours < 1 ) // if we don't have a badge, show "1"
+ nTours = 1;
+ V_snprintf( szTours, sizeof( szTours ), "%u", nTours );
+ }
+
+ m_pChallengeList->SetClickable( bLeader && bInUIState );
+ if ( idxTour < 0 )
+ {
+ m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_name", g_pVGuiLocalize->Find( "#TF_MvM_Missions" ) );
+ }
+ else
+ {
+ m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_name", g_pVGuiLocalize->Find( GetItemSchema()->GetMvmTours()[ idxTour ].m_sTourNameLocalizationToken.Get() ) );
+ }
+ m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "complete_heading", g_pVGuiLocalize->Find( bForBraggingRights ? "#TF_MvM_Complete" : "#TF_MvM_Difficulty" ) );
+ m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_level", szTours );
+ m_pMvMSelectChallengeGroupPanel->SetControlVisible( "TourLevelImage", idxTour >= 0 );
+#else // new mm
+ char szTours[ 8 ] = "";
+
+ m_pChallengeList->SetClickable( bLeader && bInUIState );
+ m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_name", g_pVGuiLocalize->Find( "#TF_MvM_Missions" ) );
+ m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "complete_heading", g_pVGuiLocalize->Find( "#TF_MvM_Difficulty" ) );
+ m_pMvMSelectChallengeGroupPanel->SetDialogVariable( "tour_level", szTours );
+ m_pMvMSelectChallengeGroupPanel->SetControlVisible( "TourLevelImage", false );
+#endif // USE_MVM_TOUR
+
+ CMvMMissionSet searchChallenges;
+ GTFGCClientSystem()->GetSearchChallenges( searchChallenges );
+
+ m_pChallengeList->RemoveAll();
+ m_pChallengeList->RemoveAllSections();
+// int iSelectChallengeItem = -1;
+
+ int nCurrentSection = -1;
+ KeyValues *kvItem = NULL;
+ int itemID = 0;
+
+ //
+ // Top section is for special multi-select checkboxes
+ //
+
+ ++nCurrentSection;
+
+ m_pChallengeList->AddSection( nCurrentSection, "dummy_any_section" );
+ m_pChallengeList->SetSectionAlwaysVisible( nCurrentSection, true );
+ m_pChallengeList->SetSectionDividerColor( nCurrentSection, Color(0,0,0,0) );
+ //m_pChallengeList->SetSectionFgColor( nCurrentSection, Color( 255, 255, 255, 255 ) );
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth );
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "spacer", "", 0, m_iChallengeSpacer );
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "display_name", "", 0, m_iChallengeNameWidth );
+ //m_pChallengeList->AddColumnToSection( nCurrentSection, "completed", "", 0, m_iChallengeCompletedWidth );
+ //m_pChallengeList->SetFontSection( nCurrentSection, m_fontChallengeListHeader );
+ //m_pChallengeList->SetSectionMinimumContentHeight( nCurrentSection, m_iMapImageHeight );
+
+ m_pChallengeList->m_vecMapImages.Purge();
+
+ // List of special multi-select options
+ struct MissionMultiSelect_t
+ {
+ int m_idxChallenge;
+ const char *m_pszDisplayName;
+ bool m_bMannUp;
+ bool m_bBootCamp;
+ };
+ static const MissionMultiSelect_t arMultiSelect[] =
+ {
+#ifdef USE_MVM_TOUR
+ { k_iPopIndex_Any, "#TF_MvM_AnyChallenge", true, false },
+// { k_iPopIndex_AnyHaunted, "#TF_MvM_AnyHauntedChallenge", false, true },
+ { k_iPopIndex_AnyNormal, "#TF_MvM_AnyNormalChallenge", false, true },
+ { k_iPopIndex_AnyIntermediate, "#TF_MvM_AnyIntermediateChallenge", false, true },
+ { k_iPopIndex_AnyAdvanced, "#TF_MvM_AnyAdvancedChallenge", false, true },
+ { k_iPopIndex_AnyExpert, "#TF_MvM_AnyExpertChallenge", false, true },
+ { k_iPopIndex_OnlyNotYetCompleted, "#TF_MvM_OnlyChallengeNotYetCompleted", true, false }
+#else // new mm
+ { k_iPopIndex_Any, "#TF_MvM_AnyChallenge", true, true },
+// { k_iPopIndex_AnyHaunted, "#TF_MvM_AnyHauntedChallenge", false, true },
+ { k_iPopIndex_AnyNormal, "#TF_MvM_AnyNormalChallenge", true, true },
+ { k_iPopIndex_AnyIntermediate, "#TF_MvM_AnyIntermediateChallenge", true, true },
+ { k_iPopIndex_AnyAdvanced, "#TF_MvM_AnyAdvancedChallenge", true, true },
+ { k_iPopIndex_AnyExpert, "#TF_MvM_AnyExpertChallenge", true, true },
+ { k_iPopIndex_OnlyNotYetCompleted, "#TF_MvM_OnlyChallengeNotYetCompleted", false, false }
+#endif // USE_MVM_TOUR
+ };
+
+ // Scan each potential multi-select option
+ for ( int i = 0 ; i < Q_ARRAYSIZE( arMultiSelect ) ; ++i )
+ {
+ const MissionMultiSelect_t &ms = arMultiSelect[i];
+
+ // Check if entry is applicable for this mode
+ if ( bForBraggingRights ? !ms.m_bMannUp : !ms.m_bBootCamp )
+ continue;
+
+ // Gather list of all missions that fit this mode
+ CMvMMissionSet msChallenges;
+ GetMvmChallengeSet( ms.m_idxChallenge, msChallenges );
+
+ // Any missions actually met the criteria for this multi-select?
+ // (e.g. we might not have any intermediate missions active right now).
+ int iCheckImage;
+ if ( msChallenges.IsEmpty() )
+ {
+ if ( ms.m_idxChallenge != k_iPopIndex_OnlyNotYetCompleted )
+ continue;
+ iCheckImage = m_iImageCheckBoxDisabled;
+ }
+ else
+ {
+
+ // Determine checkbox status. "Only not yet completed" is special
+ if ( ms.m_idxChallenge == k_iPopIndex_OnlyNotYetCompleted )
+ {
+ if ( searchChallenges == msChallenges )
+ iCheckImage = m_iImageCheckBoxYes; // all items currently selected
+ else
+ iCheckImage = m_iImageCheckBoxNo; // does not exactly match, show as a "no"
+ }
+ else
+ {
+ // Get set of checked challenges that fall under this category
+ CMvMMissionSet checked( searchChallenges );
+ checked.Intersect( msChallenges );
+
+ if ( checked == msChallenges )
+ iCheckImage = m_iImageCheckBoxYes; // all items currently selected
+ //else if ( !checked.IsEmpty() ) // Nope, don't ever show "mixed" state
+ // iCheckImage = m_iImageCheckBoxMixed; // some items currently selected
+ else
+ iCheckImage = m_iImageCheckBoxNo; // no items currently selected
+ }
+
+ }
+
+ kvItem = new KeyValues("item");
+ kvItem->SetInt( "check_box", iCheckImage );
+ kvItem->SetString( "display_name", ms.m_pszDisplayName );
+ kvItem->SetInt( "pop_index", ms.m_idxChallenge );
+ itemID = m_pChallengeList->AddItem( nCurrentSection, kvItem );
+ m_pChallengeList->SetItemFont( itemID, m_fontChallengeListItem );
+
+ Color color = s_colorChallengeForegroundEnabled;
+ if ( ms.m_idxChallenge == k_iPopIndex_AnyHaunted )
+ color = s_colorChallengeForegroundHaunted;
+ if ( iCheckImage == m_iImageCheckBoxDisabled )
+ color = s_colorChallengeForegroundDisabled;
+ m_pChallengeList->SetItemFgColor( itemID, color );
+ }
+
+
+ //
+ // Now add a section for each map
+ //
+
+ int nCurrentMap = -1;
+ FOR_EACH_VEC( GetItemSchema()->GetMvmMissions(), iMissionIndex )
+ {
+ const MvMMission_t &mission = GetItemSchema()->GetMvmMissions()[ iMissionIndex ];
+
+#ifdef USE_MVM_TOUR
+ if ( bForBraggingRights && GetItemSchema()->FindMvmMissionInTour( idxTour, iMissionIndex) < 0 ) // !KLUDGE! This is sort of crappy, we probably should iterate the tour's mission list rather than iterating the larger list with filtering
+#else // new mm
+ if ( bForBraggingRights && !searchChallenges.GetMissionBySchemaIndex( iMissionIndex ) )
+#endif // USE_MVM_TOUR
+ continue;
+
+ kvItem = new KeyValues("item");
+
+ const MvMMap_t &map = GetItemSchema()->GetMvmMaps()[ mission.m_iDisplayMapIndex ];
+
+ if ( mission.m_iDisplayMapIndex != nCurrentMap )
+ {
+ ++nCurrentSection;
+
+ m_pChallengeList->AddSection( nCurrentSection, map.m_sDisplayName.Get() );
+ m_pChallengeList->SetSectionAlwaysVisible( nCurrentSection, true );
+ m_pChallengeList->SetSectionFgColor( nCurrentSection, s_colorChallengeHeader );
+ m_pChallengeList->SetSectionDividerColor( nCurrentSection, Color(0,0,0,0) );
+#ifdef USE_MVM_TOUR
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth );
+#else // new mm
+ // for mannup, don't show check box
+ if ( bForBraggingRights )
+ {
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "spacer", "", 0, m_iChallengeCheckBoxWidth );
+ }
+ else
+ {
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "check_box", "", vgui::SectionedListPanel::COLUMN_IMAGE, m_iChallengeCheckBoxWidth );
+ }
+#endif // USE_MVM_TOUR
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "spacer", "", 0, m_iChallengeSpacer );
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "display_name", map.m_sDisplayName.Get(), 0, m_iChallengeNameWidth );
+ m_pChallengeList->AddColumnToSection( nCurrentSection, "skill", "", 0, m_iChallengeSkillWidth );
+ m_pChallengeList->SetFontSection( nCurrentSection, m_fontChallengeListHeader );
+ m_pChallengeList->SetSectionMinimumHeight( nCurrentSection, m_iMapImageHeight );
+
+ BitmapImage &img = m_pChallengeList->m_vecMapImages[ m_pChallengeList->m_vecMapImages.AddToTail() ];
+ CFmtStr sImageName("vgui/maps/menu_thumb_%s", map.m_sMap.Get() );
+ img.SetImageFile( sImageName );
+
+ nCurrentMap = mission.m_iDisplayMapIndex;
+ }
+
+ //wchar_t wszChallengeName[ 256 ];
+ //g_pVGuiLocalize->ConstructString_safe( wszChallengeName, L"%s1 (%s2)", 2,
+ // g_pVGuiLocalize->Find( mission.m_sDisplayName.Get() ), g_pVGuiLocalize->Find( mission.m_sMode.Get() ) );
+ //kvItem->SetWString( "display_name", wszChallengeName );
+ kvItem->SetString( "display_name", mission.m_sDisplayName.Get() );
+
+ bool bSelected = searchChallenges.GetMissionBySchemaIndex( iMissionIndex );
+
+ kvItem->SetInt( "check_box", bSelected ? m_iImageCheckBoxYes : m_iImageCheckBoxNo );
+ const char *pszDifficulty = "";
+#ifdef USE_MVM_TOUR
+ if ( !bForBraggingRights )
+#endif // USE_MVM_TOUR
+ {
+ pszDifficulty = GetMvMChallengeDifficultyLocName( mission.m_eDifficulty );
+ }
+ kvItem->SetString( "skill", pszDifficulty );
+ kvItem->SetInt( "pop_index", iMissionIndex );
+ itemID = m_pChallengeList->AddItem( nCurrentSection, kvItem );
+ m_pChallengeList->SetItemFont( itemID, m_fontChallengeListItem );
+
+ Color color = s_colorChallengeForegroundEnabled;
+ if ( mission.m_eDifficulty == k_EMvMChallengeDifficulty_Haunted )
+ color = s_colorChallengeForegroundHaunted;
+ m_pChallengeList->SetItemFgColor( itemID, color );
+
+ kvItem->deleteThis();
+ }
+
+ --m_iWritingPanel;
+}
+
+
+//-----------------------------------------------------------------------------
+void CLobbyPanel_MvM::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pOpenStoreButton = dynamic_cast<vgui::Button *>(FindChildByName( "OpenStoreButton", true )); Assert( m_pOpenStoreButton );
+ m_pOpenStoreButton2 = dynamic_cast<vgui::Button *>(FindChildByName( "OpenStoreButton2", true )); Assert( m_pOpenStoreButton2 );
+ m_pOpenHelpButton = dynamic_cast<vgui::Button *>(FindChildByName( "OpenHelpButton", true )); Assert( m_pOpenHelpButton );
+ m_pSquadSurplusCheckButton = dynamic_cast<vgui::CheckButton *>(FindChildByName( "SquadSurplusCheckButton", true )); Assert( m_pSquadSurplusCheckButton );
+ m_pMannUpNowButton = dynamic_cast<vgui::Button *>(FindChildByName( "MannUpNowButton", true )); Assert( m_pMannUpNowButton );
+
+ m_iImageHasTicket = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_ticket_small", true ) );
+ m_pImageList->GetImage( m_iImageHasTicket )->SetSize( m_iHasTicketWidth, m_iHasTicketWidth );
+ m_iImageNoTicket = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_no_ticket_small", true ) );
+ m_pImageList->GetImage( m_iImageNoTicket )->SetSize( m_iHasTicketWidth, m_iHasTicketWidth );
+ m_iImageSquadSurplus = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_squad_surplus_small", true ) );
+ m_pImageList->GetImage( m_iImageSquadSurplus )->SetSize( m_iSquadSurplusWidth, m_iSquadSurplusWidth );
+ m_iImageNoSquadSurplus = m_pImageList->AddImage( vgui::scheme()->GetImage( "pve/mvm_no_squad_surplus_small", true ) );
+ m_pImageList->GetImage( m_iImageNoSquadSurplus )->SetSize( m_iSquadSurplusWidth, m_iSquadSurplusWidth );
+
+ m_pChatPlayerList->SetImageList( m_pImageList, false );
+ m_pChatPlayerList->SetVisible( true );
+ m_pChallengeList->SetImageList( m_pImageList, false );
+#ifdef USE_MVM_TOUR
+ m_pTourList->SetImageList( m_pImageList, false );
+#endif // USE_MVM_TOUR
+
+ //
+ // Populate the challenge list
+ //
+
+ m_pChallengeList->AddActionSignalTarget( this );
+#ifdef USE_MVM_TOUR
+ m_pTourList->AddActionSignalTarget( this );
+#endif // USE_MVM_TOUR
+ m_pSquadSurplusCheckButton->AddActionSignalTarget( this );
+ m_pChatPlayerList->AddActionSignalTarget( this );
+ m_pOpenStoreButton->AddActionSignalTarget( this );
+ m_pOpenStoreButton2->AddActionSignalTarget( this );
+ m_pOpenHelpButton->AddActionSignalTarget( this );
+ m_pMannUpNowButton->AddActionSignalTarget( this );
+
+ m_pChallengeList->SetVerticalScrollbar( true );
+ m_pChallengeList->RemoveAll();
+ m_pChallengeList->RemoveAllSections();
+ m_pChallengeList->SetDrawHeaders( true );
+ m_pChallengeList->SetClickable( true );
+ m_pChallengeList->SetBgColor( Color( 0, 0, 0, 0 ) );
+ m_pChallengeList->SetBorder( NULL );
+
+#ifdef USE_MVM_TOUR
+ m_pTourList->SetDrawHeaders( false );
+#endif // USE_MVM_TOUR
+
+ m_fontChallengeListHeader = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_fontChallengeListItem = pScheme->GetFont( "HudFontSmallest", true );
+
+ //
+ // Populate the player list
+ //
+ int nAvatarWidth = ( ( m_iAvatarWidth * 5 / 4 ) + 1 );
+ int nExtraWidth = m_pChatPlayerList->GetWide() - nAvatarWidth - m_iPlayerNameWidth - m_iBannedWidth - m_iHasTicketWidth - m_iSquadSurplusWidth - m_iBadgeLevelWidth;
+
+ m_pChatPlayerList->AddColumnToSection( 0, "avatar", "#TF_Players", vgui::SectionedListPanel::COLUMN_IMAGE, nAvatarWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "name", "", 0, m_iPlayerNameWidth + nExtraWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "is_banned", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iBannedWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "has_ticket", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iHasTicketWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "squad_surplus", "", vgui::SectionedListPanel::COLUMN_IMAGE | vgui::SectionedListPanel::COLUMN_CENTER, m_iSquadSurplusWidth );
+ m_pChatPlayerList->AddColumnToSection( 0, "badge_level", "#TF_MvM_Tours", vgui::SectionedListPanel::COLUMN_CENTER, m_iBadgeLevelWidth );
+}
+
+void CLobbyPanel_MvM::PerformLayout()
+{
+#ifdef USE_MVM_TOUR
+ WriteTourList();
+#endif // USE_MVM_TOUR
+ WriteChallengeList();
+
+ BaseClass::PerformLayout();
+}
+
+void CLobbyPanel_MvM::OnClickedOnChallenge()
+{
+ int iSelected = m_pChallengeList->GetSelectedItem();
+ m_pChallengeList->SetSelectedItem( -1 );
+ if ( iSelected < 0 )
+ return;
+ if ( BIsPartyLeader() && BIsPartyInUIState() )
+ {
+ int iChallengeIndex = m_pChallengeList->GetItemData( iSelected )->GetInt( "pop_index", -1 );
+
+#ifndef USE_MVM_TOUR
+ // disallow player to select individual challenge in mannup
+ if ( iChallengeIndex >= 0 && GTFGCClientSystem()->GetSearchPlayForBraggingRights() )
+ return;
+#endif // !USE_MVM_TOUR
+
+ CMvMMissionSet searchChallenges;
+
+ // Fetch current selection. Except when clicking the "only uncompleted" checkbox, which is special
+ if ( iChallengeIndex != k_iPopIndex_OnlyNotYetCompleted )
+ GTFGCClientSystem()->GetSearchChallenges( searchChallenges );
+
+ CMvMMissionSet setChallenges;
+ GetMvmChallengeSet( iChallengeIndex, setChallenges );
+ bool bSelect = ( m_pChallengeList->GetItemData( iSelected )->GetInt( "check_box" ) != m_iImageCheckBoxYes );
+ for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
+ {
+ if ( setChallenges.GetMissionBySchemaIndex( i ) )
+ {
+ searchChallenges.SetMissionBySchemaIndex( i, bSelect );
+ }
+ }
+
+ GTFGCClientSystem()->SetSearchChallenges( searchChallenges );
+ }
+ else
+ {
+ WriteChallengeList();
+ }
+}
+
+
+void CLobbyPanel_MvM::ChallengeList::Paint()
+{
+ vgui::SectionedListPanel::Paint();
+
+ FOR_EACH_VEC( m_vecMapImages, i )
+ {
+ int x, y, w, h;
+ if ( !GetSectionHeaderBounds( i + 1, x, y, w, h ) )
+ {
+ Assert( "MvM map mismatch" );
+ continue;
+ }
+
+ // Dear god. Why is VGUI such a piece of crap?
+ // And why is it such excruciating pain to do
+ // anything at all?
+
+ // Select subrectangle within image to draw
+ w = m_pLobbyPanel->m_iMapImageWidth;
+ h = m_pLobbyPanel->m_iMapImageHeight;
+ m_vecMapImages[i].SetViewport( true, 0.0, 0.0, 1.0, (float)h / (float)w );
+
+ // Compute horiziontal position of icon
+ int gutter = vgui::scheme()->GetProportionalScaledValue( 5 );
+ x -= gutter + m_pLobbyPanel->m_iMapImageWidth;
+
+ // Save clipping rectangle. We want to be able to draw off to the left,
+ // outside of our bounding box, but we need to keep the vertical clipping
+ int left, top, right, bottom;
+ bool bDisabled;
+ g_pMatSystemSurface->GetClippingRect( left, top, right, bottom, bDisabled );
+
+ // Adjust clipping rectangle
+ int sx = x;
+ int sy = y;
+ LocalToScreen(sx, sy);
+ g_pMatSystemSurface->SetClippingRect( sx, top, right, bottom );
+
+ m_vecMapImages[i].DoPaint( x, y, w, h );
+
+ // Restore clipping rectangle
+ g_pMatSystemSurface->SetClippingRect( left, top, right, bottom );
+ g_pMatSystemSurface->DisableClipping( bDisabled );
+ }
+
+#ifdef USE_MVM_TOUR
+ // We can only do checkmarks if we know what tour they are working towards
+ int idxTour = GTFGCClientSystem()->GetSearchMannUpTourIndex();
+ if ( idxTour >= 0 )
+ {
+ int nCheckSize = m_pLobbyPanel->m_iChallengeCompletedSize;
+ int nCompletedX0 = m_pLobbyPanel->m_iChallengeCheckBoxWidth + m_pLobbyPanel->m_iChallengeNameWidth + m_pLobbyPanel->m_iChallengeSkillWidth / 4;
+
+ uint32 nTours, nCompletedChallenge;
+ bool bFoundPlayerCompletedChallenges = GTFGCClientSystem()->BGetLocalPlayerBadgeInfoForTour( idxTour, &nTours, &nCompletedChallenge );
+
+ for ( int i = 0 ; i < GetItemCount() ; ++i )
+ {
+ // Get The Pop File Name for this item
+ KeyValues *pkv = GetItemData( GetItemIDFromRow( i ) );
+ int iMissionIndexInSchema = pkv->GetInt( "pop_index", -1 );
+ if ( iMissionIndexInSchema < 0 ) // special multi-select entry
+ continue;
+
+ int iBadgeSlot = GetItemSchema()->GetMvmMissionBadgeSlotForTour( idxTour, iMissionIndexInSchema );
+ if ( iBadgeSlot < 0 )
+ continue;
+
+ int x, y, w, h;
+ GetItemBounds( i, x, y, w, h );
+
+ if ( bFoundPlayerCompletedChallenges )
+ {
+ if ( nCompletedChallenge & (1 << iBadgeSlot) )
+ {
+ m_imageChallengeCompleted.SetColor( Color(255,255,255,255) );
+ }
+ else if ( GetSelectedItem() == i )
+ {
+ m_imageChallengeCompleted.SetColor( Color(0,0,0,255) );
+ }
+ else
+ {
+ m_imageChallengeCompleted.SetColor( Color(0,0,0,70) );
+ }
+ int checkX0 = nCompletedX0;
+ int checkY0 = y + ( h - nCheckSize ) / 2;
+ m_imageChallengeCompleted.DoPaint( checkX0, checkY0, nCheckSize, nCheckSize );
+ }
+ }
+ }
+#endif // USE_MVM_TOUR
+}
diff --git a/game/client/tf/vgui/tf_lobbypanel_mvm.h b/game/client/tf/vgui/tf_lobbypanel_mvm.h
new file mode 100644
index 0000000..7ea3dc0
--- /dev/null
+++ b/game/client/tf/vgui/tf_lobbypanel_mvm.h
@@ -0,0 +1,151 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#ifndef TF_LOBBYPANEL_MVM_H
+#define TF_LOBBYPANEL_MVM_H
+
+
+#include "cbase.h"
+#include "game/client/iviewport.h"
+#include "vgui_bitmapimage.h"
+#include "tf_lobbypanel.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+class CLobbyPanel_MvM : public CBaseLobbyPanel
+{
+ DECLARE_CLASS_SIMPLE( CLobbyPanel_MvM, CBaseLobbyPanel );
+
+public:
+ CLobbyPanel_MvM( vgui::Panel *pParent, CBaseLobbyContainerFrame* pLobbyContainer );
+
+ virtual ~CLobbyPanel_MvM();
+
+ //
+ // Panel overrides
+ //
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+
+ //
+ // CGameEventListener overrides
+ //
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ void SetMannUpTicketCount( int nCount );
+ void SetSquadSurplusCount( int nCount );
+
+ virtual EMatchGroup GetMatchGroup( void ) const OVERRIDE;
+
+ void ToggleSquadSurplusCheckButton( void )
+ {
+ if ( !GTFGCClientSystem()->BLocalPlayerInventoryHasSquadSurplusVoucher() )
+ return;
+
+ m_pSquadSurplusCheckButton->SetSelected( !m_pSquadSurplusCheckButton->IsSelected() );
+ }
+
+private:
+
+ virtual const char* GetResFile() const OVERRIDE { return "Resource/UI/LobbyPanel_MvM.res"; } ;
+
+ CPanelAnimationVarAliasType( int, m_iChallengeSpacer, "challenge_spacer", "4", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iChallengeNameWidth, "challenge_name_width", "190", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iChallengeSkillWidth, "challenge_skill_width", "110", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iChallengeCompletedSize, "challenge_completed_size", "15", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iMapImageWidth, "challenge_map_width", "60", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iMapImageHeight, "challenge_map_height", "40", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iHasTicketWidth, "has_ticket_width", "12", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iSquadSurplusWidth, "squad_surplus_width", "12", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTourMapWidth, "squad_surplus_width", "20", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iBadgeLevelWidth, "badge_level_width", "20", "proportional_int" );
+
+ CPanelAnimationVarAliasType( int, m_iTourNameWidth, "tour_name_width", "160", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTourSkillWidth, "tour_skill_width", "90", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTourProgressWidth, "tour_progress_width", "70", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTourNumberWidth, "tour_number_width", "40", "proportional_int" );
+
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel );
+
+ MESSAGE_FUNC_PTR( OnItemLeftClick, "ItemLeftClick", panel );
+
+ virtual void ApplyChatUserSettings( const LobbyPlayerInfo& player, KeyValues* pSettings ) const OVERRIDE;
+#ifdef USE_MVM_TOUR
+ void OnClickedOnTour();
+#endif // USE_MVM_TOUR
+ void OnClickedOnChallenge();
+
+ class ChallengeList : public vgui::SectionedListPanel
+ {
+ public:
+ ChallengeList( CLobbyPanel_MvM *pLobbyPanel, vgui::Panel *parent, const char *name )
+ : vgui::SectionedListPanel( parent, name )
+ , m_pLobbyPanel( pLobbyPanel )
+ {
+ m_imageChallengeCompleted.SetImageFile( "vgui/pve/mvm_challenge_completed" );
+ }
+
+ virtual void OnMouseDoublePressed(vgui::MouseCode code) OVERRIDE { /* Just eat it */ }
+ virtual void Paint() OVERRIDE;
+
+ CLobbyPanel_MvM *m_pLobbyPanel;
+
+ BitmapImage m_imageChallengeCompleted;
+ CUtlVector<BitmapImage> m_vecMapImages;
+ };
+
+ CUtlVector<vgui::Label *> m_vecSearchCriteriaLabels;
+
+ vgui::EditablePanel *m_pMvMMannVsMachineGroupPanel;
+ vgui::EditablePanel *m_pMvMMannUpGroupPanel;
+ vgui::EditablePanel *m_pMvMPracticeGroupPanel;
+
+ vgui::EditablePanel *m_pMvMTourOfDutyGroupPanel;
+ vgui::EditablePanel *m_pMvMTourOfDutyListGroupBox;
+ vgui::SectionedListPanel *m_pTourList;
+
+ vgui::EditablePanel *m_MvMEconItemsGroupBox;
+ vgui::CheckButton *m_pSquadSurplusCheckButton;
+ vgui::Button *m_pOpenStoreButton;
+ vgui::Button *m_pOpenStoreButton2;
+ vgui::Button *m_pOpenHelpButton;
+ vgui::ImagePanel *m_pMannUpTicketImage;
+ vgui::ImagePanel *m_pSquadSurplusImage;
+ vgui::Button *m_pMannUpNowButton;
+
+ vgui::EditablePanel *m_pMannUpTourLootDescriptionBox;
+ vgui::ImagePanel *m_pMannUpTourLootImage;
+ vgui::Label *m_pTourDifficultyWarning;
+ //vgui::Label *m_pMannUpTourLootDetailLabel;
+
+ vgui::EditablePanel *m_MvMPracticeGroupPanel;
+
+ vgui::EditablePanel *m_pMvMSelectChallengeGroupPanel;
+ vgui::EditablePanel *m_pMVMChallengeListGroupBox;
+ ChallengeList *m_pChallengeList;
+
+
+ vgui::HFont m_fontChallengeListHeader;
+ vgui::HFont m_fontChallengeListItem;
+
+ int m_iImageNoTicket;
+ int m_iImageHasTicket;
+ int m_iImageNoSquadSurplus;
+ int m_iImageSquadSurplus;
+
+ void WriteGameSettingsControls() OVERRIDE;
+ virtual bool ShouldShowLateJoin() const OVERRIDE;
+#ifdef USE_MVM_TOUR
+ void WriteTourList();
+#endif // USE_MVM_TOUR
+ void WriteChallengeList();
+};
+
+#endif // TF_LOBBYPANEL_MVM_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_mapinfomenu.cpp b/game/client/tf/vgui/tf_mapinfomenu.cpp
new file mode 100644
index 0000000..5c5956d
--- /dev/null
+++ b/game/client/tf/vgui/tf_mapinfomenu.cpp
@@ -0,0 +1,648 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include <vgui_controls/ImagePanel.h>
+#include <vgui_controls/RichText.h>
+#include <game/client/iviewport.h>
+#include <vgui/ILocalize.h>
+#include <KeyValues.h>
+#include <filesystem.h>
+#include "IGameUIFuncs.h" // for key bindings
+#include "inputsystem/iinputsystem.h"
+
+#include "ixboxsystem.h"
+#include "tf_gamerules.h"
+#include "tf_controls.h"
+#include "tf_shareddefs.h"
+#include "tf_mapinfomenu.h"
+
+#include "video/ivideoservices.h"
+
+using namespace vgui;
+
+const char *GetMapDisplayName( const char *mapName );
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFMapInfoMenu::CTFMapInfoMenu( IViewPort *pViewPort ) : Frame( NULL, PANEL_MAPINFO )
+{
+ m_pViewPort = pViewPort;
+
+ // load the new scheme early!!
+ SetScheme( "ClientScheme" );
+
+ SetTitleBarVisible( false );
+ SetMinimizeButtonVisible( false );
+ SetMaximizeButtonVisible( false );
+ SetCloseButtonVisible( false );
+ SetSizeable( false );
+ SetMoveable( false );
+ SetProportional( true );
+ SetVisible( false );
+ SetKeyBoardInputEnabled( true );
+
+ m_pTitle = new CExLabel( this, "MapInfoTitle", " " );
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+#else
+ m_pContinue = new CExButton( this, "MapInfoContinue", "#TF_Continue" );
+ m_pBack = new CExButton( this, "MapInfoBack", "#TF_Back" );
+ m_pIntro = new CExButton( this, "MapInfoWatchIntro", "#TF_WatchIntro" );
+#endif
+
+ // info window about this map
+ m_pMapInfo = new CExRichText( this, "MapInfoText" );
+ m_pMapImage = new ImagePanel( this, "MapImage" );
+
+ m_szMapName[0] = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CTFMapInfoMenu::~CTFMapInfoMenu()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/MapInfoMenu_SC.res" );
+ m_pContinueHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoContinueHintIcon" ) );
+ m_pBackHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoBackHintIcon" ) );
+ m_pIntroHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoIntroHintIcon" ) );
+
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/MapInfoMenu.res" );
+ m_pContinueHintIcon = m_pBackHintIcon = m_pIntroHintIcon = nullptr;
+ SetMouseInputEnabled( true );
+ }
+
+ CheckIntroState();
+ CheckBackContinueButtons();
+
+ char mapname[MAX_MAP_NAME];
+
+ Q_FileBase( engine->GetLevelName(), mapname, sizeof(mapname) );
+
+ // Save off the map name so we can re-load the page in ApplySchemeSettings().
+ Q_strncpy( m_szMapName, mapname, sizeof( m_szMapName ) );
+ Q_strupr( m_szMapName );
+
+#ifdef _X360
+ char *pExt = Q_stristr( m_szMapName, ".360" );
+ if ( pExt )
+ {
+ *pExt = '\0';
+ }
+#endif
+
+ LoadMapPage();
+ SetMapTitle();
+
+#ifndef _X360
+ if ( m_pContinue )
+ {
+ m_pContinue->RequestFocus();
+ }
+#endif
+
+ SetDialogVariable( "gamemode", g_pVGuiLocalize->Find( GetMapType( m_szMapName ) ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::ShowPanel( bool bShow )
+{
+ if ( IsVisible() == bShow )
+ return;
+
+ m_KeyRepeat.Reset();
+
+ if ( bShow )
+ {
+ InvalidateLayout( true, true ); // Force scheme reload since the steam controller state may have changed.
+ Activate();
+ CheckIntroState();
+ }
+ else
+ {
+ SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMapInfoMenu::CheckForIntroMovie()
+{
+ const char *pVideoFileName = TFGameRules()->GetVideoFileForMap();
+ if ( pVideoFileName == NULL )
+ {
+ return false;
+ }
+
+ VideoSystem_t playbackSystem = VideoSystem::NONE;
+ char resolvedFile[MAX_PATH];
+ if ( g_pVideo && g_pVideo->LocatePlayableVideoFile( pVideoFileName, "GAME", &playbackSystem, resolvedFile, sizeof(resolvedFile) ) == VideoResult::SUCCESS )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+const char *COM_GetModDirectory();
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMapInfoMenu::HasViewedMovieForMap()
+{
+ return ( UTIL_GetMapKeyCount( "viewed" ) > 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::CheckIntroState()
+{
+ if ( CheckForIntroMovie() && HasViewedMovieForMap() )
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "intro", true );
+ }
+#else
+ if ( m_pIntro && !m_pIntro->IsVisible() )
+ {
+ m_pIntro->SetVisible( true );
+ if ( m_pIntroHintIcon )
+ {
+ m_pIntroHintIcon->SetVisible( true );
+ }
+ }
+#endif
+ }
+ else
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "intro", false );
+ }
+#else
+ if ( m_pIntro && m_pIntro->IsVisible() )
+ {
+ m_pIntro->SetVisible( false );
+ if ( m_pIntroHintIcon )
+ {
+ m_pIntroHintIcon->SetVisible( false );
+ }
+ }
+#endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::CheckBackContinueButtons()
+{
+#ifndef _X360
+ if ( m_pBack && m_pContinue )
+ {
+ if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED )
+ {
+ m_pBack->SetVisible( true );
+ if ( m_pBackHintIcon )
+ {
+ m_pBackHintIcon->SetVisible( true );
+ }
+ m_pContinue->SetText( "#TF_Continue" );
+ }
+ else
+ {
+ m_pBack->SetVisible( false );
+ if ( m_pBackHintIcon )
+ {
+ m_pBackHintIcon->SetVisible( false );
+ }
+ m_pContinue->SetText( "#TF_Close" );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::OnCommand( const char *command )
+{
+ m_KeyRepeat.Reset();
+
+ if ( !Q_strcmp( command, "back" ) )
+ {
+ // only want to go back to the Welcome menu if we're not already on a team
+ if ( !IsX360() && ( GetLocalPlayerTeam() == TEAM_UNASSIGNED ) )
+ {
+ m_pViewPort->ShowPanel( this, false );
+ m_pViewPort->ShowPanel( PANEL_INFO, true );
+ }
+ }
+ else if ( !Q_strcmp( command, "continue" ) )
+ {
+ m_pViewPort->ShowPanel( this, false );
+
+ if ( CheckForIntroMovie() && !HasViewedMovieForMap() )
+ {
+ m_pViewPort->ShowPanel( PANEL_INTRO, true );
+
+ UTIL_IncrementMapKey( "viewed" );
+ }
+ else
+ {
+ // On console, we may already have a team due to the lobby assigning us one.
+ // We tell the server we're done with the map info menu, and it decides what to do with us.
+ if ( IsX360() )
+ {
+ engine->ClientCmd( "closedwelcomemenu" );
+ }
+ else if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED )
+ {
+ if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
+ {
+ m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true );
+ }
+ else
+ {
+ engine->ClientCmd( "team_ui_setup" );
+ }
+ }
+
+ UTIL_IncrementMapKey( "viewed" );
+ }
+ }
+ else if ( !Q_strcmp( command, "intro" ) )
+ {
+ m_pViewPort->ShowPanel( this, false );
+
+ if ( CheckForIntroMovie() )
+ {
+ m_pViewPort->ShowPanel( PANEL_INTRO, true );
+ }
+ else
+ {
+ if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
+ {
+ m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true );
+ }
+ else
+ {
+ engine->ClientCmd( "team_ui_setup" );
+ }
+ }
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::Update()
+{
+ InvalidateLayout( false, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: chooses and loads the text page to display that describes mapName map
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::LoadMapPage()
+{
+ if ( !m_szMapName[0] )
+ {
+ m_pMapInfo->SetText( "" );
+ m_pMapImage->SetVisible( false );
+ return;
+ }
+
+ // load the map image (if it exists for the current map)
+ char szMapImage[ MAX_PATH ];
+ Q_snprintf( szMapImage, sizeof( szMapImage ), "VGUI/maps/menu_photos_%s", m_szMapName );
+ Q_strlower( szMapImage );
+
+ IMaterial *pMapMaterial = materials->FindMaterial( szMapImage, TEXTURE_GROUP_VGUI, false );
+ if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) )
+ {
+ if ( m_pMapImage )
+ {
+ if ( !m_pMapImage->IsVisible() )
+ {
+ m_pMapImage->SetVisible( true );
+ }
+
+ // take off the vgui/ at the beginning when we set the image
+ Q_snprintf( szMapImage, sizeof( szMapImage ), "maps/menu_photos_%s", m_szMapName );
+ Q_strlower( szMapImage );
+
+ m_pMapImage->SetImage( szMapImage );
+ }
+ }
+ else
+ {
+ if ( m_pMapImage && m_pMapImage->IsVisible() )
+ {
+ m_pMapImage->SetVisible( false );
+ }
+ }
+
+ // try loading map descriptions from the localization files first
+ char mapDescriptionKey[ 64 ];
+ Q_snprintf( mapDescriptionKey, sizeof( mapDescriptionKey ), "#%s_description", m_szMapName );
+ Q_strlower( mapDescriptionKey );
+ wchar_t* wszMapDescription = g_pVGuiLocalize->Find( mapDescriptionKey );
+ if( wszMapDescription )
+ {
+ m_pMapInfo->SetText( wszMapDescription );
+ }
+ else
+ {
+ // try loading map descriptions from .txt files first
+ char mapRES[ MAX_PATH ];
+
+ char uilanguage[ 64 ];
+ uilanguage[0] = 0;
+ engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
+
+ Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s_%s.txt", m_szMapName, uilanguage );
+
+ // try English if the file doesn't exist for our language
+ if( !g_pFullFileSystem->FileExists( mapRES, "GAME" ) )
+ {
+ Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s_english.txt", m_szMapName );
+
+ // if the file doesn't exist for English either, try the filename without any language extension
+ if( !g_pFullFileSystem->FileExists( mapRES, "GAME" ) )
+ {
+ Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s.txt", m_szMapName );
+ }
+ }
+
+ // if no map specific description exists, load default text
+ if( g_pFullFileSystem->FileExists( mapRES, "GAME" ) )
+ {
+ FileHandle_t f = g_pFullFileSystem->Open( mapRES, "rb" );
+
+ // read into a memory block
+ int fileSize = g_pFullFileSystem->Size(f);
+ int dataSize = fileSize + sizeof( wchar_t );
+ if ( dataSize % 2 )
+ ++dataSize;
+ wchar_t *memBlock = (wchar_t *)malloc(dataSize);
+ memset( memBlock, 0x0, dataSize);
+ int bytesRead = g_pFullFileSystem->Read(memBlock, fileSize, f);
+ if ( bytesRead < fileSize )
+ {
+ // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
+ // return fewer bytes than we were expecting.
+ char *data = reinterpret_cast<char *>( memBlock );
+ data[ bytesRead ] = 0;
+ data[ bytesRead+1 ] = 0;
+ }
+
+ #ifndef WIN32
+ if ( ((ucs2 *)memBlock)[0] == 0xFEFF )
+ {
+ // convert the win32 ucs2 data to wchar_t
+ dataSize*=2;// need to *2 to account for ucs2 to wchar_t (4byte) growth
+ wchar_t *memBlockConverted = (wchar_t *)malloc(dataSize);
+ V_UCS2ToUnicode( (ucs2 *)memBlock, memBlockConverted, dataSize );
+ free(memBlock);
+ memBlock = memBlockConverted;
+ }
+ #else
+ // null-terminate the stream (redundant, since we memset & then trimmed the transformed buffer already)
+ memBlock[dataSize / sizeof(wchar_t) - 1] = 0x0000;
+ #endif
+ // check the first character, make sure this a little-endian unicode file
+
+ #if defined( _X360 )
+ if ( memBlock[0] != 0xFFFE )
+ #else
+ if ( memBlock[0] != 0xFEFF )
+ #endif
+ {
+ // its a ascii char file
+ m_pMapInfo->SetText( reinterpret_cast<char *>( memBlock ) );
+ }
+ else
+ {
+ // ensure little-endian unicode reads correctly on all platforms
+ CByteswap byteSwap;
+ byteSwap.SetTargetBigEndian( false );
+ byteSwap.SwapBufferToTargetEndian( memBlock, memBlock, dataSize/sizeof(wchar_t) );
+
+ m_pMapInfo->SetText( memBlock+1 );
+ }
+ // go back to the top of the text buffer
+ m_pMapInfo->GotoTextStart();
+
+ g_pFullFileSystem->Close( f );
+ free(memBlock);
+ }
+ else
+ {
+ // try loading map descriptions from localization files next
+ const char *pszDescription = NULL;
+ char mapInfoKey[ 64 ];
+
+ if ( TFGameRules() && TFGameRules()->IsPowerupMode() && ( FStrEq( m_szMapName, "ctf_foundry" ) || FStrEq( m_szMapName, "ctf_gorge" ) ) )
+ {
+ Q_snprintf( mapInfoKey, sizeof( mapInfoKey ), "#%s_beta", m_szMapName );
+ }
+ else
+ {
+ Q_snprintf( mapInfoKey, sizeof( mapInfoKey ), "#%s", m_szMapName );
+ }
+
+ Q_strlower( mapInfoKey );
+
+ if( !g_pVGuiLocalize->Find( mapInfoKey ) )
+ {
+ if ( TFGameRules() )
+ {
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ pszDescription = "#default_mvm_description";
+ }
+ else
+ {
+ switch ( TFGameRules()->GetGameType() )
+ {
+ case TF_GAMETYPE_CTF:
+ pszDescription = "#default_ctf_description";
+ break;
+ case TF_GAMETYPE_CP:
+ if ( TFGameRules()->IsInKothMode() )
+ {
+ pszDescription = "#default_koth_description";
+ }
+ else
+ {
+ pszDescription = "#default_cp_description";
+ }
+ break;
+ case TF_GAMETYPE_ESCORT:
+ if ( TFGameRules()->HasMultipleTrains() )
+ {
+ pszDescription = "#default_payload_race_description";
+ }
+ else
+ {
+ pszDescription = "#default_payload_description";
+ }
+ break;
+ case TF_GAMETYPE_ARENA:
+ pszDescription = "#default_arena_description";
+ break;
+ case TF_GAMETYPE_RD:
+ pszDescription = "#default_rd_description";
+ break;
+ case TF_GAMETYPE_PASSTIME:
+ pszDescription = "#default_passtime_description";
+ break;
+ case TF_GAMETYPE_PD:
+ pszDescription = "#default_pd_description";
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ pszDescription = mapInfoKey;
+ }
+
+ if ( pszDescription && pszDescription[0] )
+ {
+ m_pMapInfo->SetText( pszDescription );
+ }
+ else
+ {
+ m_pMapInfo->SetText( "" );
+ }
+ }
+ }
+
+ // we haven't loaded a valid map image for the current map
+ if ( m_pMapImage && !m_pMapImage->IsVisible() )
+ {
+ if ( m_pMapInfo )
+ {
+ m_pMapInfo->SetWide( m_pMapInfo->GetWide() + ( m_pMapImage->GetWide() * 0.75 ) ); // add in the extra space the images would have taken
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::SetMapTitle()
+{
+ SetDialogVariable( "mapname", GetMapDisplayName( m_szMapName ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::OnKeyCodePressed( KeyCode code )
+{
+ m_KeyRepeat.KeyDown( code );
+
+ if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
+ {
+ OnCommand( "continue" );
+ }
+ else if ( code == STEAMCONTROLLER_B )
+ {
+ OnCommand( "back" );
+ }
+ else if ( code == KEY_XBUTTON_Y || code == STEAMCONTROLLER_Y )
+ {
+ OnCommand( "intro" );
+ }
+ else if( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == STEAMCONTROLLER_DPAD_UP )
+ {
+ // Scroll class info text up
+ if ( m_pMapInfo )
+ {
+ PostMessage( m_pMapInfo, new KeyValues("MoveScrollBarDirect", "delta", 1) );
+ }
+ }
+ else if( code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == STEAMCONTROLLER_DPAD_DOWN )
+ {
+ // Scroll class info text up
+ if ( m_pMapInfo )
+ {
+ PostMessage( m_pMapInfo, new KeyValues("MoveScrollBarDirect", "delta", -1) );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::OnKeyCodeReleased( vgui::KeyCode code )
+{
+ m_KeyRepeat.KeyUp( code );
+
+ BaseClass::OnKeyCodeReleased( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapInfoMenu::OnThink()
+{
+ vgui::KeyCode code = m_KeyRepeat.KeyRepeated();
+ if ( code )
+ {
+ OnKeyCodePressed( code );
+ }
+
+ //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
+ }
+
+ BaseClass::OnThink();
+}
+
diff --git a/game/client/tf/vgui/tf_mapinfomenu.h b/game/client/tf/vgui/tf_mapinfomenu.h
new file mode 100644
index 0000000..4f982ea
--- /dev/null
+++ b/game/client/tf/vgui/tf_mapinfomenu.h
@@ -0,0 +1,85 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_MAPINFOMENU_H
+#define TF_MAPINFOMENU_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Frame.h>
+#include "vgui_controls/KeyRepeat.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: displays the MapInfo menu
+//-----------------------------------------------------------------------------
+
+class CTFMapInfoMenu : public vgui::Frame, public IViewPortPanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFMapInfoMenu, vgui::Frame );
+
+public:
+ CTFMapInfoMenu( IViewPort *pViewPort );
+ virtual ~CTFMapInfoMenu();
+
+ virtual const char *GetName( void ){ return PANEL_MAPINFO; }
+ virtual void SetData( KeyValues *data ){}
+ virtual void Reset(){ Update(); }
+ virtual void Update();
+ virtual bool NeedsUpdate( void ){ return false; }
+ virtual bool HasInputElements( void ){ return true; }
+ virtual void ShowPanel( bool bShow );
+
+ // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui
+ vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); }
+ virtual bool IsVisible(){ return BaseClass::IsVisible(); }
+ virtual void SetParent( vgui::VPANEL parent ){ BaseClass::SetParent( parent ); }
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; }
+
+protected:
+ virtual void OnKeyCodePressed(vgui::KeyCode code);
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodeReleased( vgui::KeyCode code );
+ virtual void OnThink();
+
+private:
+ // helper functions
+ void LoadMapPage();
+ void SetMapTitle();
+ bool HasViewedMovieForMap();
+ bool CheckForIntroMovie();
+ void CheckIntroState();
+ void CheckBackContinueButtons();
+
+protected:
+ IViewPort *m_pViewPort;
+ CExLabel *m_pTitle;
+ CExRichText *m_pMapInfo;
+
+#ifdef _X360
+ CTFFooter *m_pFooter;
+#else
+ CExButton *m_pContinue;
+ CExButton *m_pBack;
+ CExButton *m_pIntro;
+ CSCHintIcon *m_pContinueHintIcon;
+ CSCHintIcon *m_pBackHintIcon;
+ CSCHintIcon *m_pIntroHintIcon;
+#endif
+
+ vgui::ImagePanel *m_pMapImage;
+
+ char m_szMapName[MAX_PATH];
+
+ vgui::CKeyRepeatHandler m_KeyRepeat;
+};
+
+
+#endif // TF_MAPINFOMENU_H
diff --git a/game/client/tf/vgui/tf_match_join_handlers.cpp b/game/client/tf/vgui/tf_match_join_handlers.cpp
new file mode 100644
index 0000000..408c464
--- /dev/null
+++ b/game/client/tf/vgui/tf_match_join_handlers.cpp
@@ -0,0 +1,145 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "tf_shareddefs.h"
+#include "tf_match_join_handlers.h"
+#include "tf_gc_client.h"
+#include "tf_controls.h"
+#include "tf_gamerules.h"
+#include "ienginevgui.h"
+#include "clientmode_tf.h"
+#include "tf_hud_disconnect_prompt.h"
+#include <vgui_controls/AnimationController.h>
+#include "vgui_int.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+#include "../vgui2/src/VPanel.h"
+
+using namespace vgui;
+
+#define MM_REJOIN_WAIT_TIME 1.0f
+#define MM_REJOIN_PROMPT_TIMEOUT 3.f
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IMatchJoiningHandler::IMatchJoiningHandler()
+{}
+
+IMatchJoiningHandler::~IMatchJoiningHandler()
+{}
+
+void IMatchJoiningHandler::JoinMatch()
+{
+ GTFGCClientSystem()->JoinMMMatch();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFImmediateAutoJoinHandler::CTFImmediateAutoJoinHandler()
+ : m_flNextAutoJoinTime( 0.f )
+{}
+
+void CTFImmediateAutoJoinHandler::MatchFound()
+{
+ // No special logic. Just try to join every 2 seconds
+ if ( Plat_FloatTime() > m_flNextAutoJoinTime && !GTFGCClientSystem()->BConnectedToMatchServer( false ) )
+ {
+ JoinMatch();
+ m_flNextAutoJoinTime = Plat_FloatTime() + 2.f;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFRejoinConfirmDialog* CTFMatchMakingPopupPrompJoinHandler::m_pRejoinPrompt = NULL;
+
+CTFMatchMakingPopupPrompJoinHandler::CTFMatchMakingPopupPrompJoinHandler()
+ : m_flNextRejoinThinkTime( 0.f )
+{}
+
+
+void CTFMatchMakingPopupPrompJoinHandler::MatchFound()
+{
+ if ( !m_pRejoinPrompt )
+ {
+ if ( m_flNextRejoinThinkTime == 0.f || Plat_FloatTime() >= ( m_flNextRejoinThinkTime + MM_REJOIN_PROMPT_TIMEOUT ) )
+ {
+ m_flNextRejoinThinkTime = Plat_FloatTime() + MM_REJOIN_WAIT_TIME;
+ }
+ else if ( Plat_FloatTime() > m_flNextRejoinThinkTime )
+ {
+ UpdatePromptState();
+ m_flNextRejoinThinkTime = 0.f;
+ }
+ }
+}
+
+void CTFMatchMakingPopupPrompJoinHandler::OnJoinLobbyInProgressCallback( bool bConfirmed, void *pContext )
+{
+ GTFGCClientSystem()->RejoinLobby( bConfirmed );
+ m_pRejoinPrompt = NULL;
+}
+
+void CTFMatchMakingPopupPrompJoinHandler::UpdatePromptState()
+{
+ bool bHasMatch = GTFGCClientSystem()->BHaveLiveMatch();
+ bool bInLiveMatch = GTFGCClientSystem()->BConnectedToMatchServer( false );
+
+ if ( !m_pRejoinPrompt && bHasMatch && !bInLiveMatch )
+ {
+ if ( enginevgui == NULL || GetClientModeTFNormal()->GameUI() == NULL )
+ return;
+
+ // Check if this player is in Abandon territory, if so warn them
+ EAbandonGameStatus eAbandonStatus = GTFGCClientSystem()->GetAssignedMatchAbandonStatus();
+ const char* pszTitle = "#TF_MM_Rejoin_Title";
+ const char* pszBody = NULL;
+ const char* pszConfirm = "#TF_MM_Rejoin_Confirm";
+ const char* pszCancel = NULL;
+
+ switch ( eAbandonStatus )
+ {
+ case k_EAbandonGameStatus_Safe:
+ pszBody = "#TF_MM_Rejoin_BaseText";
+ pszCancel = "#TF_MM_Rejoin_Leave";
+ break;
+ case k_EAbandonGameStatus_AbandonWithoutPenalty:
+ pszBody = "#TF_MM_Rejoin_AbandonText_NoPenalty";
+ pszCancel = "#TF_MM_Rejoin_Abandon";
+ break;
+ case k_EAbandonGameStatus_AbandonWithPenalty:
+ pszBody = "#TF_MM_Rejoin_AbandonText";
+ pszCancel = "#TF_MM_Rejoin_Abandon";
+ break;
+ }
+
+ m_pRejoinPrompt = vgui::SETUP_PANEL( new CTFRejoinConfirmDialog(
+ pszTitle,
+ pszBody,
+ pszConfirm,
+ pszCancel,
+ &OnJoinLobbyInProgressCallback,
+ NULL
+ ));
+
+ if ( m_pRejoinPrompt )
+ {
+ m_pRejoinPrompt->Show();
+ // VGUI is being dumb so I need to manually calculate this windows position
+ int sW, sT, dW, dT;
+ vgui::surface()->GetScreenSize( sW, sT );
+ m_pRejoinPrompt->GetSize( dW, dT );
+ m_pRejoinPrompt->SetPos( (sW - dW) / 2, (sT - dT) / 2 );
+ }
+ }
+}
diff --git a/game/client/tf/vgui/tf_match_join_handlers.h b/game/client/tf/vgui/tf_match_join_handlers.h
new file mode 100644
index 0000000..efe0153
--- /dev/null
+++ b/game/client/tf/vgui/tf_match_join_handlers.h
@@ -0,0 +1,58 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_MATCH_JOIN_HANDLERS_H
+#define TF_MATCH_JOIN_HANDLERS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/EditablePanel.h>
+#include "tf_controls.h"
+
+class IMatchJoiningHandler
+{
+public:
+ IMatchJoiningHandler();
+ virtual ~IMatchJoiningHandler();
+
+ virtual void MatchFound() = 0;
+
+protected:
+
+ void JoinMatch();
+};
+
+class CTFImmediateAutoJoinHandler : public IMatchJoiningHandler
+{
+public:
+ CTFImmediateAutoJoinHandler();
+ virtual void MatchFound() OVERRIDE;
+
+private:
+
+ float m_flNextAutoJoinTime;
+};
+
+class CTFRejoinConfirmDialog;
+class CTFMatchMakingPopupPrompJoinHandler : public IMatchJoiningHandler
+{
+public:
+ CTFMatchMakingPopupPrompJoinHandler();
+
+ virtual void MatchFound() OVERRIDE;
+
+ static void OnJoinLobbyInProgressCallback( bool bConfirmed, void *pContext );
+private:
+
+ void UpdatePromptState();
+
+ static CTFRejoinConfirmDialog* m_pRejoinPrompt;
+ float m_flNextRejoinThinkTime;
+};
+
+#endif // TF_MATCH_JOIN_HANDLERS_H
diff --git a/game/client/tf/vgui/tf_match_summary.cpp b/game/client/tf/vgui/tf_match_summary.cpp
new file mode 100644
index 0000000..4f5e4be
--- /dev/null
+++ b/game/client/tf/vgui/tf_match_summary.cpp
@@ -0,0 +1,1481 @@
+
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "hud.h"
+#include "tf_match_summary.h"
+#include "tf_hud_statpanel.h"
+#include "tf_spectatorgui.h"
+#include "vgui_controls/AnimationController.h"
+#include "iclientmode.h"
+#include "engine/IEngineSound.h"
+#include "c_tf_playerresource.h"
+#include "c_team.h"
+#include "tf_clientscoreboard.h"
+#include <vgui_controls/Label.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui/ILocalize.h>
+#include <vgui/ISurface.h>
+#include "vgui_avatarimage.h"
+#include "fmtstr.h"
+#include "teamplayroundbased_gamerules.h"
+#include "tf_gamerules.h"
+#include "tf_logic_halloween_2014.h"
+#include "tf_playermodelpanel.h"
+#include "tf_mapinfo.h"
+#include "c_tf_team.h"
+#include "tf_pvp_rank_panel.h"
+#include "tf_badge_panel.h"
+#include "tf_survey_questions.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
+extern ISoundEmitterSystemBase *soundemitterbase;
+
+#define MAX_PLAYER_MODELS 6
+
+#define MS_STATE_TRANSITION_TO_STATS 17.0f
+#define MS_STATE_TRANSITION_TO_MEDALS 3.0f
+#define MS_STATE_TIME_BETWEEN_MEDALS 0.1f
+#define MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES 0.1f
+
+extern ConVar tf_scoreboard_alt_class_icons;
+
+DECLARE_BUILD_FACTORY( TFSectionedListPanel );
+
+DECLARE_HUDELEMENT( CTFMatchSummary );
+
+#ifdef STAGING_ONLY
+static void cc_tf_restart_match_summary()
+{
+ CTFMatchSummary *pMatchSummary = GET_HUDELEMENT( CTFMatchSummary );
+ if (pMatchSummary)
+ {
+ pMatchSummary->InvalidateLayout(true, true);
+ pMatchSummary->SetVisible( false );
+ pMatchSummary->SetVisible( true );
+ }
+}
+ConCommand tf_restart_match_summary("tf_restart_match_summary", cc_tf_restart_match_summary);
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFMatchSummary::CTFMatchSummary( const char *pElementName )
+ : CHudElement( pElementName )
+ , EditablePanel( NULL, "MatchSummary" )
+ , m_bXPShown( false )
+{
+ vgui::Panel *pParent = g_pClientMode->GetViewport();
+ SetParent( pParent );
+
+ m_pMainStatsContainer = new EditablePanel( this, "MainStatsContainer" );
+ m_pDrawingPanel = new CDrawingPanel( this, "DrawingPanel" );
+ m_pStatsBgPanel = new EditablePanel( this, "StatsBgPanel" );
+
+ m_pTeamScoresPanel = new EditablePanel( m_pMainStatsContainer, "TeamScoresPanel" );
+ m_pParticlePanel = new CTFParticlePanel( m_pMainStatsContainer, "ParticlePanel" );
+ m_pStatsLabelPanel = new EditablePanel( m_pMainStatsContainer, "StatsLabelPanel" );
+
+ m_pBlueTeamPanel = new EditablePanel( m_pTeamScoresPanel, "BlueTeamPanel" );
+ m_pRedTeamPanel = new EditablePanel( m_pTeamScoresPanel, "RedTeamPanel" );
+
+ m_pPlayerListBlueParent = new EditablePanel( m_pBlueTeamPanel, "BluePlayerListParent" );
+ m_pPlayerListBlue = new TFSectionedListPanel( m_pPlayerListBlueParent, "BluePlayerList" );
+ m_pPlayerListRedParent = new EditablePanel( m_pRedTeamPanel, "RedPlayerListParent" );
+ m_pPlayerListRed = new TFSectionedListPanel( m_pPlayerListRedParent, "RedPlayerList" );
+
+ m_pBlueTeamScore = new CExLabel( m_pBlueTeamPanel, "BlueTeamScore", "" );
+ m_pBlueTeamScoreDropshadow = new CExLabel( m_pBlueTeamPanel, "BlueTeamScoreDropshadow", "" );
+ m_pBlueTeamScoreBG = new EditablePanel( m_pBlueTeamPanel, "BlueTeamScoreBG" );
+ m_pBluePlayerListBG = new EditablePanel( m_pBlueTeamPanel, "BluePlayerListBG" );
+ m_pRedTeamScore = new CExLabel( m_pRedTeamPanel, "RedTeamScore", "" );
+ m_pRedTeamScoreDropshadow = new CExLabel( m_pRedTeamPanel, "RedTeamScoreDropshadow", "" );
+ m_pRedTeamScoreBG = new EditablePanel( m_pRedTeamPanel, "RedTeamScoreBG" );
+ m_pRedPlayerListBG = new EditablePanel( m_pRedTeamPanel, "RedPlayerListBG" );
+ m_pBlueMedalsPanel = new EditablePanel( m_pTeamScoresPanel, "BlueMedals" );
+ m_pRedMedalsPanel = new EditablePanel( m_pTeamScoresPanel, "RedMedals" );
+ m_pRedTeamImage = new vgui::ImagePanel( m_pRedTeamPanel, "RedTeamImage" );
+ m_pBlueTeamImage = new vgui::ImagePanel( m_pBlueTeamPanel, "BlueTeamImage" );
+ m_pRedLeaderAvatarImage = new CAvatarImagePanel( m_pRedTeamPanel, "RedLeaderAvatar" );
+ m_pBlueLeaderAvatarImage = new CAvatarImagePanel( m_pBlueTeamPanel, "BlueLeaderAvatar" );
+ m_pRedLeaderAvatarBG = new EditablePanel( m_pRedTeamPanel, "RedLeaderAvatarBG" );
+ m_pBlueLeaderAvatarBG = new EditablePanel( m_pBlueTeamPanel, "BlueLeaderAvatarBG" );
+ m_pStatsAndMedals = new CExLabel( m_pStatsLabelPanel, "StatsAndMedals", "" );
+ m_pStatsAndMedalsShadow = new CExLabel( m_pStatsLabelPanel, "StatsAndMedalsShadow", "" );
+ m_pRedTeamName = new CExLabel( m_pRedTeamPanel, "RedTeamLabel", "" );
+ m_pBlueTeamName = new CExLabel( m_pBlueTeamPanel, "BlueTeamLabel", "" );
+ m_pRedTeamWinner = new CExLabel( m_pRedTeamPanel, "RedTeamWinner", "" );
+ m_pRedTeamWinnerDropshadow = new CExLabel( m_pRedTeamPanel, "RedTeamWinnerDropshadow", "" );
+ m_pBlueTeamWinner = new CExLabel( m_pBlueTeamPanel, "BlueTeamWinner", "" );
+ m_pBlueTeamWinnerDropshadow = new CExLabel( m_pBlueTeamPanel, "BlueTeamWinnerDropshadow", "" );
+
+ m_pImageList = NULL;
+
+ m_mapAvatarsToImageList.SetLessFunc( DefLessFunc( CSteamID ) );
+ m_mapAvatarsToImageList.RemoveAll();
+
+ Q_memset( m_SkillRatings, 0, sizeof( m_SkillRatings ) );
+
+ m_iCurrentState = MS_STATE_INITIAL;
+ m_flNextActionTime = -1;
+
+ m_nMedalsToAward_Bronze_Blue = 0;
+ m_nMedalsToAward_Silver_Blue = 0;
+ m_nMedalsToAward_Gold_Blue = 0;
+ m_nMedalsToAward_Bronze_Red = 0;
+ m_nMedalsToAward_Silver_Red = 0;
+ m_nMedalsToAward_Gold_Red = 0;
+
+ m_nMedalsRevealed = 0;
+ m_nNumMedalsThisUpdate = 0;
+
+ m_bBlueGoldValueRevealed = false;
+ m_bBlueSilverValueRevealed = false;
+ m_bBlueBronzeValueRevealed = false;
+ m_bRedGoldValueRevealed = false;
+ m_bRedSilverValueRevealed = false;
+ m_bRedBronzeValueRevealed = false;
+ m_bPlayerAbandoned = false;
+
+ m_flMedalSoundTime = -1.f;
+
+ m_bLargeMatchGroup = false;
+
+ Q_memset( m_iImageClass, NULL, sizeof( m_iImageClass ) );
+ Q_memset( m_iImageClassAlt, NULL, sizeof( m_iImageClassAlt ) );
+
+ ListenForGameEvent( "competitive_victory" );
+ ListenForGameEvent( "competitive_stats_update" );
+ ListenForGameEvent( "player_abandoned_match" );
+ ListenForGameEvent( "client_disconnect" );
+ ListenForGameEvent( "show_match_summary" );
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 50 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFMatchSummary::~CTFMatchSummary()
+{
+ if ( NULL != m_pImageList )
+ {
+ delete m_pImageList;
+ m_pImageList = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Applies scheme settings
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_bLargeMatchGroup = false;
+
+ KeyValues *pConditions = NULL;
+ if ( TFGameRules() )
+ {
+ const IMatchGroupDescription* pMatch = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
+ if ( pMatch )
+ {
+ if ( pMatch->m_params.m_pmm_match_group_size->GetInt() > 12 )
+ {
+ pConditions = new KeyValues( "conditions" );
+ AddSubKeyNamed( pConditions, "if_large" );
+
+ m_bLargeMatchGroup = true;
+ }
+ }
+ }
+
+ LoadControlSettings( "resource/UI/HudMatchSummary.res", NULL, NULL, pConditions );
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ }
+
+ if ( m_pDrawingPanel )
+ {
+ m_pDrawingPanel->ClearAllLines();
+ m_pDrawingPanel->SetType( DRAWING_PANEL_TYPE_MATCH_SUMMARY );
+ m_pDrawingPanel->MakePopup();
+ }
+
+
+ if ( m_pImageList )
+ delete m_pImageList;
+ m_pImageList = new ImageList( false );
+
+ m_mapAvatarsToImageList.RemoveAll();
+
+ for ( int i = 1; i < (int)StatMedal_Max; i++ )
+ {
+ m_iImageMedals[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszCompetitiveMedalImages[i], true ) );
+ }
+
+ for ( int i = 1; i < SCOREBOARD_CLASS_ICONS; i++ )
+ {
+ m_iImageClass[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIcons[i], true ) );
+ m_iImageClassAlt[i] = m_pImageList->AddImage( scheme()->GetImage( g_pszClassIconsAlt[i], true ) );
+ }
+
+ int iCurrentCount = m_pImageList->GetImageCount();
+
+ // resize the images to our resolution
+ for ( int i = 0; i < iCurrentCount; i++ )
+ {
+ int wide = 13, tall = 13;
+ m_pImageList->GetImage( i )->SetSize( scheme()->GetProportionalScaledValueEx( GetScheme(), wide ), scheme()->GetProportionalScaledValueEx( GetScheme(), tall ) );
+ }
+
+ // resize the images to our resolution
+ for ( int i = iCurrentCount; i < m_pImageList->GetImageCount(); i++ )
+ {
+ int wide = 26, tall = 26;
+ m_pImageList->GetImage( i )->SetSize( scheme()->GetProportionalScaledValueEx( GetScheme(), wide ), scheme()->GetProportionalScaledValueEx( GetScheme(), tall ) );
+ }
+
+ SetPaintBackgroundEnabled( false );
+ m_pTeamScoresPanel->SetPaintBackgroundEnabled( false );
+ m_pPlayerListBlueParent->SetPaintBackgroundEnabled( false );
+ m_pPlayerListRedParent->SetPaintBackgroundEnabled( false );
+ m_pPlayerListBlue->SetPaintBackgroundEnabled( false );
+ m_pPlayerListRed->SetPaintBackgroundEnabled( false );
+
+ m_pPlayerListBlue->SetImageList( m_pImageList, false );
+ m_pPlayerListBlue->SetVisible( true );
+
+ m_pPlayerListRed->SetImageList( m_pImageList, false );
+ m_pPlayerListRed->SetVisible( true );
+
+ InitPlayerList( m_pPlayerListBlue, TF_TEAM_BLUE );
+ InitPlayerList( m_pPlayerListRed, TF_TEAM_RED );
+
+ m_hFont = pScheme->GetFont( "ScoreboardVerySmall", true );
+
+ m_iCurrentState = MS_STATE_INITIAL;
+ m_flNextActionTime = -1;
+
+ RecalculateMedalCounts();
+
+ m_nMedalsRevealed = 0;
+
+ m_bBlueGoldValueRevealed = false;
+ m_bBlueSilverValueRevealed = false;
+ m_bBlueBronzeValueRevealed = false;
+ m_bRedGoldValueRevealed = false;
+ m_bRedSilverValueRevealed = false;
+ m_bRedBronzeValueRevealed = false;
+
+ m_flMedalSoundTime = -1.f;
+ m_flDrawingPanelTime = -1.f;
+
+ m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_gold", "?" );
+ m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_silver", "?" );
+ m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_bronze", "?" );
+ m_pRedMedalsPanel->SetDialogVariable( "redteammedals_gold", "?" );
+ m_pRedMedalsPanel->SetDialogVariable( "redteammedals_silver", "?" );
+ m_pRedMedalsPanel->SetDialogVariable( "redteammedals_bronze", "?" );
+
+ Update();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ EditablePanel* pStatsContainer = FindControl< EditablePanel >( "MainStatsContainer" );
+ if ( pStatsContainer && m_bLargeMatchGroup )
+ {
+ pStatsContainer->SetPos( pStatsContainer->GetXPos(), m_iAnimStatsContainer12v12YPos );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMatchSummary::ShouldDraw( void )
+{
+ return IsVisible();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::SetVisible( bool state )
+{
+ int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "mid" );
+
+ if ( state )
+ {
+ gHUD.LockRenderGroup( iRenderGroup );
+
+ InvalidateLayout( true, true );
+
+ m_iCurrentState = MS_STATE_INITIAL;
+
+ m_flDrawingPanelTime = gpGlobals->curtime + 4.5f;
+
+ CPvPRankPanel* pPvPRankPanel = FindControl< CPvPRankPanel >( "RankPanel" );
+ if ( pPvPRankPanel )
+ {
+ pPvPRankPanel->SetMatchGroup( TFGameRules()->GetCurrentMatchGroup() );
+ }
+
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "CompetitiveGame_LowerChatWindow", false );
+ }
+ else
+ {
+ gHUD.UnlockRenderGroup( iRenderGroup );
+ }
+
+ BaseClass::SetVisible( state );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for sorting players
+//-----------------------------------------------------------------------------
+bool CTFMatchSummary::TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 )
+{
+ KeyValues *it1 = list->GetItemData( itemID1 );
+ KeyValues *it2 = list->GetItemData( itemID2 );
+ Assert( it1 && it2 );
+
+ // first compare score
+ int v1 = it1->GetInt( "score" );
+ int v2 = it2->GetInt( "score" );
+ if ( v1 > v2 )
+ return true;
+ else if ( v1 < v2 )
+ return false;
+
+ // if score is the same, use player index to get deterministic sort
+ int iPlayerIndex1 = it1->GetInt( "playerIndex" );
+ int iPlayerIndex2 = it2->GetInt( "playerIndex" );
+ return ( iPlayerIndex1 > iPlayerIndex2 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inits the player list in a list panel
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::InitPlayerList( TFSectionedListPanel *pPlayerList, int nTeam )
+{
+ float flAspectRatio = engine->GetScreenAspectRatio();
+ bool bStandard = flAspectRatio < 1.6f;
+
+ pPlayerList->SetVerticalScrollbar( false );
+ pPlayerList->RemoveAll();
+ pPlayerList->RemoveAllSections();
+ pPlayerList->AddSection( 0, "Players", TFPlayerSortFunc );
+ pPlayerList->SetSectionAlwaysVisible( 0, true );
+ pPlayerList->SetSectionDrawDividerBar( 0, false );
+ pPlayerList->SetBorder( NULL );
+ pPlayerList->SetMouseInputEnabled( false );
+ pPlayerList->SetClickable( false );
+
+ pPlayerList->AddColumnToSection( 0, "medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, pPlayerList->m_iMedalWidth );
+ pPlayerList->AddColumnToSection( 0, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAvatarWidth );
+ pPlayerList->AddColumnToSection( 0, "spacer", "", 0, pPlayerList->m_iSpacerWidth );
+ pPlayerList->AddColumnToSection( 0, "name", "", 0, pPlayerList->m_iNameWidth );
+ pPlayerList->AddColumnToSection( 0, "class", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iClassWidth );
+ pPlayerList->AddColumnToSection( 0, "score", bStandard ? "#TF_Comp_Scoreboard_Score_Standard" : "#TF_Comp_Scoreboard_Score", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth );
+ pPlayerList->AddColumnToSection( 0, "score_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth );
+ pPlayerList->AddColumnToSection( 0, "kills", bStandard ? "#TF_Comp_Scoreboard_Kills_Standard" : "#TF_Comp_Scoreboard_Kills", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth );
+ pPlayerList->AddColumnToSection( 0, "kills_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth );
+ pPlayerList->AddColumnToSection( 0, "damage", bStandard ? "#TF_Comp_Scoreboard_Damage_Standard" : "#TF_Comp_Scoreboard_Damage", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth );
+ pPlayerList->AddColumnToSection( 0, "damage_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth );
+ pPlayerList->AddColumnToSection( 0, "healing", bStandard ? "#TF_Comp_Scoreboard_Healing_Standard" : "#TF_Comp_Scoreboard_Healing", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth );
+ pPlayerList->AddColumnToSection( 0, "healing_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth );
+ pPlayerList->AddColumnToSection( 0, "support", bStandard ? "#TF_Comp_Scoreboard_Support_Standard" : "#TF_Comp_Scoreboard_Support", SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iStatsWidth );
+ pPlayerList->AddColumnToSection( 0, "support_medal", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, pPlayerList->m_iAwardWidth );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::Update( void )
+{
+ UpdateTeamInfo();
+ UpdatePlayerList();
+ UpdateBadgePanels( m_pRedBadgePanels, m_pPlayerListRed );
+ UpdateBadgePanels( m_pBlueBadgePanels, m_pPlayerListBlue );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates information about teams
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::UpdateTeamInfo()
+{
+ bool bUseWinnerLabel = false;
+ if ( GetGlobalTFTeam( TF_TEAM_RED ) && GetGlobalTFTeam( TF_TEAM_BLUE ) )
+ {
+ if ( GetGlobalTFTeam( TF_TEAM_RED )->Get_Score() == GetGlobalTFTeam( TF_TEAM_BLUE )->Get_Score() )
+ {
+ bUseWinnerLabel = true;
+ }
+ }
+
+ int nWinningTeam = TEAM_INVALID;
+ if ( TFGameRules() )
+ {
+ nWinningTeam = TFGameRules()->GetWinningTeam();
+ }
+
+ for ( int teamIndex = TF_TEAM_RED; teamIndex <= TF_TEAM_BLUE; teamIndex++ )
+ {
+ C_TFTeam *team = GetGlobalTFTeam( teamIndex );
+ if ( team )
+ {
+ // choose dialog variables to set depending on team
+ const char *pDialogVarTeamName = "";
+ const char *pDialogVarTeamScore = "";
+ const char *pDialogVarWinner = "";
+ vgui::EditablePanel *pOwner = NULL;
+
+ switch ( teamIndex )
+ {
+ case TF_TEAM_RED:
+ pDialogVarTeamName = "redteamname";
+ pDialogVarTeamScore = "redteamscore";
+ pDialogVarWinner = "redteamwinner";
+ pOwner = m_pRedTeamPanel;
+ break;
+ case TF_TEAM_BLUE:
+ pDialogVarTeamName = "blueteamname";
+ pDialogVarTeamScore = "blueteamscore";
+ pDialogVarWinner = "blueteamwinner";
+ pOwner = m_pBlueTeamPanel;
+ break;
+ default:
+ Assert( false );
+ break;
+ }
+
+ if ( !pOwner )
+ return;
+
+ // set the team name
+ pOwner->SetDialogVariable( pDialogVarTeamName, team->Get_Localized_Name() );
+
+ if ( bUseWinnerLabel )
+ {
+ const char *pszLabel = "";
+ if ( teamIndex == nWinningTeam )
+ {
+ if ( team->GetNumPlayers() > 1 )
+ {
+ pszLabel = "#TF_Winners";
+ }
+ else
+ {
+ pszLabel = "#TF_Winner";
+ }
+ }
+
+ pOwner->SetDialogVariable( pDialogVarTeamScore, "" );
+ pOwner->SetDialogVariable( pDialogVarWinner, g_pVGuiLocalize->Find( pszLabel ) );
+ }
+ else
+ {
+ pOwner->SetDialogVariable( pDialogVarTeamScore, team->Get_Score() );
+ pOwner->SetDialogVariable( pDialogVarWinner, "" );
+ }
+ }
+ }
+
+ bool bShowAvatars = g_TF_PR && g_TF_PR->HasPremadeParties();
+
+ if ( bShowAvatars )
+ {
+ m_pRedLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderRedTeamIndex() ), k_EAvatarSize64x64 );
+ m_pRedLeaderAvatarImage->SetShouldDrawFriendIcon( false );
+ m_pBlueLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderBlueTeamIndex() ), k_EAvatarSize64x64 );
+ m_pBlueLeaderAvatarImage->SetShouldDrawFriendIcon( false );
+ }
+
+ m_pRedLeaderAvatarImage->SetVisible( bShowAvatars );
+ m_pRedLeaderAvatarBG->SetVisible( bShowAvatars );
+ m_pRedTeamName->SetVisible( bShowAvatars );
+ m_pRedTeamImage->SetVisible( !bShowAvatars );
+
+ m_pBlueLeaderAvatarImage->SetVisible( bShowAvatars );
+ m_pBlueLeaderAvatarBG->SetVisible( bShowAvatars );
+ m_pBlueTeamName->SetVisible( bShowAvatars );
+ m_pBlueTeamImage->SetVisible( !bShowAvatars );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the last medal (column) added so we can display some effects
+//-----------------------------------------------------------------------------
+matchsummary_columns_t CTFMatchSummary::InternalAddMedalKeyValues( int iIndex, StatMedal_t eMedal, KeyValues *pKeyValues, int nTotalMedals /*= -1*/ )
+{
+ int nMedal = (int)eMedal;
+ matchsummary_columns_t retVal = MS_COLUMN_INVALID;
+
+ if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) )
+ {
+ if ( m_SkillRatings[iIndex].nScoreRank == nMedal )
+ {
+ pKeyValues->SetInt( "score_medal", m_iImageMedals[nMedal] );
+ if ( nTotalMedals >= 0 )
+ {
+ m_nNumMedalsThisUpdate++;
+ }
+
+ if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) )
+ {
+ retVal = MS_COLUMN_SCORE_MEDAL;
+ }
+ }
+ }
+
+ if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) )
+ {
+ if ( m_SkillRatings[iIndex].nKillsRank == nMedal )
+ {
+ pKeyValues->SetInt( "kills_medal", m_iImageMedals[nMedal] );
+ if ( nTotalMedals >= 0 )
+ {
+ m_nNumMedalsThisUpdate++;
+ }
+
+ if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) )
+ {
+ retVal = MS_COLUMN_KILLS_MEDAL;
+ }
+ }
+ }
+
+ if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) )
+ {
+ if ( m_SkillRatings[iIndex].nDamageRank == nMedal )
+ {
+ pKeyValues->SetInt( "damage_medal", m_iImageMedals[nMedal] );
+ if ( nTotalMedals >= 0 )
+ {
+ m_nNumMedalsThisUpdate++;
+ }
+
+ if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) )
+ {
+ retVal = MS_COLUMN_DAMAGE_MEDAL;
+ }
+ }
+ }
+
+ if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) )
+ {
+ if ( m_SkillRatings[iIndex].nHealingRank == nMedal )
+ {
+ pKeyValues->SetInt( "healing_medal", m_iImageMedals[nMedal] );
+ if ( nTotalMedals >= 0 )
+ {
+ m_nNumMedalsThisUpdate++;
+ }
+
+ if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) )
+ {
+ retVal = MS_COLUMN_HEALING_MEDAL;
+ }
+ }
+ }
+
+ if ( ( nTotalMedals < 0 ) || ( m_nNumMedalsThisUpdate <= nTotalMedals ) )
+ {
+ if ( m_SkillRatings[iIndex].nSupportRank == nMedal )
+ {
+ pKeyValues->SetInt( "support_medal", m_iImageMedals[nMedal] );
+ if ( nTotalMedals >= 0 )
+ {
+ m_nNumMedalsThisUpdate++;
+ }
+
+ if ( ( nTotalMedals >= 0 ) && ( m_nNumMedalsThisUpdate > nTotalMedals ) )
+ {
+ retVal = MS_COLUMN_SUPPORT_MEDAL;
+ }
+ }
+ }
+
+ return retVal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the player list
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::UpdatePlayerList()
+{
+ m_pPlayerListRed->RemoveAll();
+ m_pPlayerListRed->ClearAllColorOverrideForCell();
+
+ m_pPlayerListBlue->RemoveAll();
+ m_pPlayerListBlue->ClearAllColorOverrideForCell();
+
+ if ( !g_TF_PR )
+ return;
+
+ m_nNumMedalsThisUpdate = 0;
+
+ for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
+ {
+ if ( g_PR->IsConnected( playerIndex ) || g_PR->IsValid( playerIndex ) )
+ {
+ TFSectionedListPanel *pPlayerList = NULL;
+ int nTeam = g_PR->GetTeam( playerIndex );
+ switch ( nTeam )
+ {
+ case TF_TEAM_BLUE:
+ pPlayerList = m_pPlayerListBlue;
+ break;
+ case TF_TEAM_RED:
+ pPlayerList = m_pPlayerListRed;
+ break;
+ }
+ if ( null == pPlayerList )
+ continue;
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "playerIndex", playerIndex );
+
+ // this is just a placeholder in the sectioned list panel
+ pKeyValues->SetInt( "medal", 0 );
+
+ pKeyValues->SetString( "name", g_TF_PR->GetPlayerName( playerIndex ) );
+ pKeyValues->SetInt( "score", g_TF_PR->GetTotalScore( playerIndex ) );
+
+ int iClass = g_TF_PR->GetPlayerClass( playerIndex );
+ if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS )
+ {
+ pKeyValues->SetInt( "class", tf_scoreboard_alt_class_icons.GetBool() ? m_iImageClassAlt[iClass] : m_iImageClass[iClass] );
+ }
+ else
+ {
+ pKeyValues->SetInt( "class", 0 );
+ }
+
+ pKeyValues->SetInt( "kills", g_TF_PR->GetPlayerScore( playerIndex ) );
+ pKeyValues->SetInt( "damage", g_TF_PR->GetDamage( playerIndex ) );
+ pKeyValues->SetInt( "healing", g_TF_PR->GetHealing( playerIndex ) );
+
+ int nSupport = g_TF_PR->GetDamageAssist( playerIndex ) +
+ g_TF_PR->GetHealingAssist( playerIndex ) +
+ g_TF_PR->GetDamageBlocked( playerIndex ) +
+ ( g_TF_PR->GetBonusPoints( playerIndex ) * 25 );
+ pKeyValues->SetInt( "support", nSupport );
+
+ matchsummary_columns_t eParticleColumn = MS_COLUMN_INVALID;
+ StatMedal_t eParticleMedal = StatMedal_None;
+
+ if ( m_iCurrentState == MS_STATE_GOLD_MEDALS )
+ {
+ // we can add the bronze and silver since we've already processed those
+ InternalAddMedalKeyValues( playerIndex, StatMedal_Bronze, pKeyValues );
+ InternalAddMedalKeyValues( playerIndex, StatMedal_Silver, pKeyValues );
+ eParticleColumn = InternalAddMedalKeyValues( playerIndex, StatMedal_Gold, pKeyValues, m_nMedalsRevealed );
+ eParticleMedal = StatMedal_Gold;
+ }
+ else if ( m_iCurrentState == MS_STATE_SILVER_MEDALS )
+ {
+ // we can add the bronze since we've already processed those
+ InternalAddMedalKeyValues( playerIndex, StatMedal_Bronze, pKeyValues );
+ eParticleColumn = InternalAddMedalKeyValues( playerIndex, StatMedal_Silver, pKeyValues, m_nMedalsRevealed );
+ eParticleMedal = StatMedal_Silver;
+ }
+ else if ( m_iCurrentState == MS_STATE_BRONZE_MEDALS )
+ {
+ eParticleColumn = InternalAddMedalKeyValues( playerIndex, StatMedal_Bronze, pKeyValues, m_nMedalsRevealed );
+ eParticleMedal = StatMedal_Bronze;
+ }
+
+ UpdatePlayerAvatar( playerIndex, pKeyValues );
+
+ int itemID = pPlayerList->AddItem( 0, pKeyValues );
+ pPlayerList->SetItemFgColor( itemID, g_PR->GetTeamColor( nTeam ) );
+ // Green background for rematch folks
+ pPlayerList->SetItemBgColor( itemID, Color( 80, 80, 80, 80 ) );
+ pPlayerList->SetItemBgHorizFillInset( itemID, pPlayerList->m_iHorizFillInset );
+ pPlayerList->SetItemFont( itemID, m_hFont );
+
+ // This highlights the local player in grey
+ if ( playerIndex == GetLocalPlayerIndex() )
+ {
+ pPlayerList->SetSelectedItem( itemID );
+ }
+
+ // loop through and setup our medal color overrides
+ KeyValues *pKey = pKeyValues->FindKey( "score_medal" );
+ if ( pKey && pKey->GetInt() )
+ {
+ int nMedal = pKey->GetInt();
+ pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_SCORE, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal );
+ }
+ pKey = pKeyValues->FindKey( "kills_medal" );
+ if ( pKey && pKey->GetInt() )
+ {
+ int nMedal = pKey->GetInt();
+ pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_KILLS, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal );
+ }
+ pKey = pKeyValues->FindKey( "damage_medal" );
+ if ( pKey && pKey->GetInt() )
+ {
+ int nMedal = pKey->GetInt();
+ pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_DAMAGE, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal );
+ }
+ pKey = pKeyValues->FindKey( "healing_medal" );
+ if ( pKey && pKey->GetInt() )
+ {
+ int nMedal = pKey->GetInt();
+ pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_HEALING, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal );
+ }
+ pKey = pKeyValues->FindKey( "support_medal" );
+ if ( pKey && pKey->GetInt() )
+ {
+ int nMedal = pKey->GetInt();
+ pPlayerList->SetColorOverrideForCell( 0, itemID, MS_COLUMN_SUPPORT, ( nMedal == (int)StatMedal_Bronze ) ? m_clrBronzeMedal : ( nMedal == (int)StatMedal_Silver ) ? m_clrSilverMedal : m_clrGoldMedal );
+ }
+
+ pKeyValues->deleteThis();
+
+ if ( ( eParticleColumn > MS_COLUMN_INVALID ) && ( eParticleMedal > StatMedal_None ) )
+ {
+ int nPanelXPos, nPanelYPos, nPanelWide, nPanelTall;
+ pPlayerList->GetMaxCellBounds( itemID, eParticleColumn, nPanelXPos, nPanelYPos, nPanelWide, nPanelTall );
+
+ FireMedalEffects( pPlayerList, nPanelXPos, nPanelYPos, nPanelWide, nPanelTall, eParticleMedal );
+ }
+ }
+ }
+
+ m_pPlayerListRed->SetSectionFgColor( 0, g_PR->GetTeamColor( TF_TEAM_RED ) );
+ m_pPlayerListBlue->SetSectionFgColor( 0, g_PR->GetTeamColor( TF_TEAM_BLUE ) );
+
+ // force the lists to PerformLayout() now so we can update our rank images after we return
+ m_pPlayerListRed->InvalidateLayout( true );
+ m_pPlayerListBlue->InvalidateLayout( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::FireMedalEffects( Panel *pPanel, int nPanelXPos, int nPanelYPos, int nPanelWide, int nPanelTall, StatMedal_t eParticleMedal )
+{
+ if ( !pPanel )
+ return;
+
+ int nPanelCenterX = nPanelXPos + ( nPanelWide / 2 );
+ int nPanelCenterY = nPanelYPos + ( nPanelTall / 2 );
+
+ int iItemAbsX, iItemAbsY;
+ vgui::ipanel()->GetAbsPos( pPanel->GetParent()->GetVPanel(), iItemAbsX, iItemAbsY );
+
+ int x = iItemAbsX + nPanelCenterX;
+ int y = iItemAbsY + nPanelCenterY;
+
+ const char *pszSoundEffect = "MatchMaking.None";
+ const char *pszParticleEffect = "mvm_loot_smoke";
+ if ( eParticleMedal == StatMedal_Bronze )
+ {
+ pszSoundEffect = "MatchMaking.Bronze";
+ pszParticleEffect = "mvm_loot_explosion";
+ }
+ else if ( eParticleMedal == StatMedal_Silver )
+ {
+ pszSoundEffect = "MatchMaking.Silver";
+ pszParticleEffect = "mvm_loot_explosion";
+ }
+ else if ( eParticleMedal == StatMedal_Gold )
+ {
+ pszSoundEffect = "MatchMaking.Gold";
+ pszParticleEffect = "mvm_pow_gold_seq_firework_mid";
+ }
+
+ m_pParticlePanel->FireParticleEffect( pszParticleEffect, x, y, 0.2f, false );
+
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->EmitSound( pszSoundEffect );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, TFSectionedListPanel *pPlayerList )
+{
+ if ( !TFGameRules() )
+ return;
+
+ const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
+ const IProgressionDesc *pProgressionDesc = pMatchDesc ? pMatchDesc->m_pProgressionDesc : NULL;
+ if ( pProgressionDesc )
+ {
+ if ( pPlayerList )
+ {
+ int iNumPanels = 0;
+ int parentTall = pPlayerList->GetTall();
+ CTFBadgePanel *pPanel = NULL;
+
+ for ( int i = 0; i < pPlayerList->GetItemCount(); i++ )
+ {
+ KeyValues *pKeyValues = pPlayerList->GetItemData( i );
+ if ( !pKeyValues )
+ continue;
+
+ const CSteamID steamID = GetSteamIDForPlayerIndex( pKeyValues->GetInt( "playerIndex" ) );
+#ifdef STAGING_ONLY
+ if ( steamID.IsValid() || tf_test_match_summary.GetBool() )
+#else
+ if ( steamID.IsValid() )
+#endif // STAGING_ONLY
+ {
+ if ( iNumPanels >= pBadgePanels.Count() )
+ {
+ pPanel = new CTFBadgePanel( m_pMainStatsContainer, "BadgePanel" );
+ pPanel->MakeReadyForUse();
+ pPanel->SetVisible( true );
+ pPanel->SetZPos( 9999 );
+ pBadgePanels.AddToTail( pPanel );
+ }
+ else
+ {
+ pPanel = pBadgePanels[iNumPanels];
+ }
+
+ int x, y, wide, tall;
+ pPlayerList->GetMaxCellBounds( i, 0, x, y, wide, tall );
+
+ if ( y + tall > parentTall )
+ continue;
+
+ if ( !pPanel->IsVisible() )
+ {
+ pPanel->SetVisible( true );
+ }
+
+ int xParent = 0, yParent = 0;
+ if ( pPlayerList->GetParent() )
+ {
+ pPlayerList->GetParent()->GetPos( xParent, yParent );
+ }
+
+ int xGrandParent = 0, yGrandParent = 0;
+ if ( pPlayerList->GetParent()->GetParent() )
+ {
+ pPlayerList->GetParent()->GetParent()->GetPos( xGrandParent, yGrandParent );
+ }
+
+ int nPanelXPos, nPanelYPos, nPanelWide, nPanelTall;
+ pPanel->GetBounds( nPanelXPos, nPanelYPos, nPanelWide, nPanelTall );
+
+ if ( ( nPanelXPos != xGrandParent + xParent + x )
+ || ( nPanelYPos != yGrandParent + yParent + y )
+ || ( nPanelWide != wide )
+ || ( nPanelTall != tall ) )
+ {
+ pPanel->SetBounds( xGrandParent + xParent + x, yGrandParent + yParent + y, wide, tall );
+ pPanel->InvalidateLayout( true, true );
+ }
+
+ pPanel->SetupBadge( pProgressionDesc, steamID );
+ iNumPanels++;
+ }
+ }
+
+ // hide any unused images
+ for ( int i = iNumPanels; i < pBadgePanels.Count(); i++ )
+ {
+ if ( pBadgePanels[i]->IsVisible() )
+ {
+ pBadgePanels[i]->SetVisible( false );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::UpdatePlayerAvatar( int playerIndex, KeyValues *kv )
+{
+ if ( !g_PR )
+ return;
+
+ uint32 iAccountID = g_PR->GetAccountID( playerIndex );
+ if ( iAccountID > 0 )
+ {
+ // Update their avatar
+ if ( kv && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() )
+ {
+ CSteamID steamIDForPlayer( iAccountID, 1, GetUniverse(), k_EAccountTypeIndividual );
+
+ // See if we already have that avatar in our list
+ int iMapIndex = m_mapAvatarsToImageList.Find( steamIDForPlayer );
+ int iImageIndex;
+ if ( iMapIndex == m_mapAvatarsToImageList.InvalidIndex() )
+ {
+ CAvatarImage *pImage = new CAvatarImage();
+ pImage->SetAvatarSteamID( steamIDForPlayer );
+ pImage->SetAvatarSize( 32, 32 ); // Deliberately non scaling
+ iImageIndex = m_pImageList->AddImage( pImage );
+
+ m_mapAvatarsToImageList.Insert( steamIDForPlayer, iImageIndex );
+ }
+ else
+ {
+ iImageIndex = m_mapAvatarsToImageList[iMapIndex];
+ }
+
+ kv->SetInt( "avatar", iImageIndex );
+
+ CAvatarImage *pAvIm = (CAvatarImage *)m_pImageList->GetImage( iImageIndex );
+ pAvIm->UpdateFriendStatus();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Event handler
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::FireGameEvent( IGameEvent *event )
+{
+ const char *type = event->GetName();
+
+ if ( FStrEq( type, "competitive_victory" ) )
+ {
+ Q_memset( m_SkillRatings, 0, sizeof( m_SkillRatings ) );
+ Leaderboards_LadderRefresh();
+ }
+ else if ( FStrEq( type, "competitive_stats_update" ) )
+ {
+ int iIndex = event->GetInt( "index" );
+ Assert( iIndex > 0 && iIndex <= MAX_PLAYERS );
+ if ( iIndex > 0 && iIndex <= MAX_PLAYERS )
+ {
+ m_SkillRatings[iIndex].unRating = event->GetInt( "rating" ); // Rank
+ m_SkillRatings[iIndex].nDelta = event->GetInt( "delta" );
+ m_SkillRatings[iIndex].nScoreRank = event->GetInt( "score_rank" ); // Medal for Score (Gold, Silver, Bronze, or nothing)
+ m_SkillRatings[iIndex].nKillsRank = event->GetInt( "kills_rank" ); // Medal for Kills
+ m_SkillRatings[iIndex].nDamageRank = event->GetInt( "damage_rank" ); // Medal for Damage
+ m_SkillRatings[iIndex].nHealingRank = event->GetInt( "healing_rank" ); // Medal for Healing
+ m_SkillRatings[iIndex].nSupportRank = event->GetInt( "support_rank" ); // Medal for Rank
+
+ RecalculateMedalCounts();
+ }
+ }
+ else if ( FStrEq( type, "player_abandoned_match" ) )
+ {
+ m_bPlayerAbandoned = true;
+ }
+ else if ( FStrEq( type, "client_disconnect" ) )
+ {
+ m_bPlayerAbandoned = false;
+ }
+ else if ( FStrEq( type, "show_match_summary" ) )
+ {
+ SetVisible( true );
+
+ if ( m_pPlayerListRedParent->GetParent() )
+ {
+ int nRedBadgeOffset = m_pPlayerListRedParent->GetParent()->GetXPos();
+ FOR_EACH_VEC( m_pRedBadgePanels, i )
+ {
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedBadgePanels[i], "xpos", m_pRedBadgePanels[i]->GetXPos() - nRedBadgeOffset, 0.25, 0.25, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ }
+ }
+
+ if ( m_pPlayerListBlueParent->GetParent() )
+ {
+ int nBlueBadgeOffset = m_pPlayerListBlueParent->GetParent()->GetXPos();
+ FOR_EACH_VEC( m_pBlueBadgePanels, i )
+ {
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueBadgePanels[i], "xpos", m_pBlueBadgePanels[i]->GetXPos() - nBlueBadgeOffset, 0.25, 0.25, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ }
+ }
+
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pTeamScoresPanel, "HudMatchSummary_SlideInPanels", false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::InternalUpdateMedalCountForType( int iTeam, StatMedal_t eMedal )
+{
+ switch ( eMedal )
+ {
+ case StatMedal_Bronze:
+ if ( iTeam == TF_TEAM_RED )
+ {
+ m_nMedalsToAward_Bronze_Red++;
+ }
+ else if ( iTeam == TF_TEAM_BLUE )
+ {
+ m_nMedalsToAward_Bronze_Blue++;
+ }
+ break;
+ case StatMedal_Silver:
+ if ( iTeam == TF_TEAM_RED )
+ {
+ m_nMedalsToAward_Silver_Red++;
+ }
+ else if ( iTeam == TF_TEAM_BLUE )
+ {
+ m_nMedalsToAward_Silver_Blue++;
+ }
+ break;
+ case StatMedal_Gold:
+ if ( iTeam == TF_TEAM_RED )
+ {
+ m_nMedalsToAward_Gold_Red++;
+ }
+ else if ( iTeam == TF_TEAM_BLUE )
+ {
+ m_nMedalsToAward_Gold_Blue++;
+ }
+ break;
+ case StatMedal_None:
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::RecalculateMedalCounts()
+{
+ m_nMedalsToAward_Bronze_Blue = 0;
+ m_nMedalsToAward_Silver_Blue = 0;
+ m_nMedalsToAward_Gold_Blue = 0;
+ m_nMedalsToAward_Bronze_Red = 0;
+ m_nMedalsToAward_Silver_Red = 0;
+ m_nMedalsToAward_Gold_Red = 0;
+
+ if ( !g_PR )
+ return;
+
+ for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
+ {
+ if ( g_PR->IsConnected( playerIndex ) || g_PR->IsValid( playerIndex ) )
+ {
+ int nTeam = g_PR->GetTeam( playerIndex );
+ if ( nTeam >= FIRST_GAME_TEAM )
+ {
+ InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nScoreRank ) );
+ InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nKillsRank ) );
+ InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nDamageRank ) );
+ InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nHealingRank ) );
+ InternalUpdateMedalCountForType( nTeam, (StatMedal_t)( m_SkillRatings[playerIndex].nSupportRank ) );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::OnTick()
+{
+ BaseClass::OnTick();
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( !IsVisible() || !pLocalPlayer )
+ return;
+
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
+ if ( !pMatchDesc )
+ return;
+
+ if ( pMatchDesc->m_params.m_bAllowDrawingAtMatchSummary
+ && m_pDrawingPanel
+ && ( m_flDrawingPanelTime > 0 )
+ && ( m_flDrawingPanelTime < gpGlobals->curtime ) )
+ {
+ m_pDrawingPanel->SetVisible( true );
+ m_pDrawingPanel->SetKeyBoardInputEnabled( false );
+ m_flDrawingPanelTime = -1.f;
+
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->EmitSound( "Announcer.SummaryScreenWinners" );
+ }
+ }
+
+ int nMedalsToAward_Bronze_Total = m_nMedalsToAward_Bronze_Blue + m_nMedalsToAward_Bronze_Red;
+ int nMedalsToAward_Silver_Total = m_nMedalsToAward_Silver_Blue + m_nMedalsToAward_Silver_Red;
+ int nMedalsToAward_Gold_Total = m_nMedalsToAward_Gold_Blue + m_nMedalsToAward_Gold_Red;
+
+ bool bShowPerformanceMedals = ShowPerformanceMedals();
+ bool bMapHasMatchSummaryStage = ( TFGameRules() && TFGameRules()->MapHasMatchSummaryStage() );
+
+
+#ifdef STAGING_ONLY
+ bool bUseMatchSummaryStage = tf_test_match_summary.GetBool() || ( pMatchDesc && pMatchDesc->m_params.m_bUseMatchSummaryStage );
+#else
+ bool bUseMatchSummaryStage = ( pMatchDesc && pMatchDesc->m_params.m_bUseMatchSummaryStage );
+#endif
+
+ switch ( m_iCurrentState )
+ {
+ case MS_STATE_INITIAL:
+ {
+ bool bUseStage = ( bMapHasMatchSummaryStage && bUseMatchSummaryStage );
+
+ if ( GTFGCClientSystem()->GetSurveyRequest().has_match_id() )
+ {
+ Panel* pSurveyPanel = CreateSurveyQuestionPanel( this, GTFGCClientSystem()->GetSurveyRequest() );
+ pSurveyPanel->MakePopup();
+ }
+
+ m_iCurrentState = MS_STATE_DRAWING;
+ m_flNextActionTime = bUseStage ? gpGlobals->curtime + MS_STATE_TRANSITION_TO_STATS : gpGlobals->curtime + 2.f;
+ m_bXPShown = false;
+
+ if ( !bUseStage )
+ {
+ // if we're not using the stage we'll just show the doors and then skip to stats with no drawing
+ if ( m_pDrawingPanel )
+ {
+ m_pDrawingPanel->SetVisible( false );
+ }
+ }
+ break;
+ }
+ case MS_STATE_DRAWING:
+ {
+ if ( gpGlobals->curtime > m_flNextActionTime )
+ {
+ if ( m_pDrawingPanel )
+ {
+ m_pDrawingPanel->SetVisible( false );
+ }
+
+ if ( m_pStatsBgPanel )
+ {
+ m_pStatsBgPanel->SetVisible( true );
+ }
+
+ if ( m_pStatsLabelPanel )
+ {
+ m_pStatsLabelPanel->SetVisible( true );
+ }
+
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pStatsLabelPanel, "ypos", m_bLargeMatchGroup ? m_iAnimStatsLabelPanel12v12YPos : m_iAnimStatsLabelPanel6v6YPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueMedalsPanel, "ypos", m_iAnimBlueMedalsYPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedMedalsPanel, "ypos", m_iAnimRedMedalsYPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamName, "ypos", m_bLargeMatchGroup ? m_iAnimBlueTeamLabel12v12YPos : m_iAnimBlueTeamLabel6v6YPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamName, "ypos", m_bLargeMatchGroup ? m_iAnimRedTeamLabel12v12YPos : m_iAnimRedTeamLabel6v6YPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pPlayerListBlueParent, "wide", m_iAnimBluePlayerListParent, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamScore, "wide", m_iAnimBlueTeamScore, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamScoreDropshadow, "wide", m_iAnimBlueTeamScoreDropshadow, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamScoreBG, "wide", m_iAnimBlueTeamScoreBG, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBluePlayerListBG, "wide", m_iAnimBluePlayerListBG, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScore, "wide", m_iAnimRedTeamScoreWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScore, "xpos", m_iAnimRedTeamScoreXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreDropshadow, "wide", m_iAnimRedTeamScoreDropshadowWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreDropshadow, "xpos", m_iAnimRedTeamScoreDropshadowXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreBG, "wide", m_iAnimRedTeamScoreBGWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamScoreBG, "xpos", m_iAnimRedTeamScoreBGXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pPlayerListRedParent, "wide", m_iAnimRedPlayerListParentWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pPlayerListRedParent, "xpos", m_iAnimRedPlayerListParentXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedPlayerListBG, "wide", m_iAnimRedPlayerListBGWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedPlayerListBG, "xpos", m_iAnimRedPlayerListBGXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamWinner, "wide", m_iAnimBlueTeamScore, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pBlueTeamWinnerDropshadow, "wide", m_iAnimBlueTeamScoreDropshadow, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinner, "wide", m_iAnimRedTeamScoreWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinner, "xpos", m_iAnimRedTeamScoreXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinnerDropshadow, "wide", m_iAnimRedTeamScoreDropshadowWide, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedTeamWinnerDropshadow, "xpos", m_iAnimRedTeamScoreDropshadowXPos, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+
+ int nRedBadgeOffset = m_pPlayerListRedParent->GetXPos() - m_iAnimRedPlayerListParentXPos;
+ FOR_EACH_VEC( m_pRedBadgePanels, i )
+ {
+ g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( m_pRedBadgePanels[i], "xpos", m_pRedBadgePanels[i]->GetXPos() - nRedBadgeOffset, 0.0, 0.1, vgui::AnimationController::INTERPOLATOR_ACCEL );
+
+ }
+
+ float flDelay = MS_STATE_TRANSITION_TO_MEDALS;
+
+ if ( pLocalPlayer )
+ {
+ if ( bShowPerformanceMedals )
+ {
+ const char *pszEntryName = UTIL_GetRandomSoundFromEntry( "Announcer.CompSummaryScreenOutlierQuestion" );
+ if ( pszEntryName && pszEntryName[0] )
+ {
+ flDelay = enginesound->GetSoundDuration( pszEntryName );
+ pLocalPlayer->EmitSound( pszEntryName );
+ }
+ }
+ pLocalPlayer->EmitSound( "MatchMaking.ScoreboardPanelSlide" );
+ }
+
+ m_iCurrentState = MS_STATE_STATS;
+ m_flNextActionTime = gpGlobals->curtime + flDelay;
+
+ m_pRedMedalsPanel->SetVisible( bShowPerformanceMedals );
+ m_pBlueMedalsPanel->SetVisible( bShowPerformanceMedals );
+
+ m_pStatsAndMedals->SetText( bShowPerformanceMedals ? g_pVGuiLocalize->Find( "#TF_CompSummary_StatsAndMedals" ) : L"" );
+ m_pStatsAndMedalsShadow->SetText( bShowPerformanceMedals ? g_pVGuiLocalize->Find( "#TF_CompSummary_StatsAndMedals" ) : L"" );
+ }
+ break;
+ }
+ case MS_STATE_STATS:
+ {
+ if ( gpGlobals->curtime > m_flNextActionTime )
+ {
+ m_iCurrentState = bShowPerformanceMedals ? MS_STATE_BRONZE_MEDALS : MS_STATE_FINAL;
+ m_nMedalsRevealed = 0;
+ m_flNextActionTime = -1;
+ }
+ break;
+ }
+ case MS_STATE_BRONZE_MEDALS:
+ {
+ if ( gpGlobals->curtime > m_flNextActionTime )
+ {
+ if ( !m_bBlueBronzeValueRevealed && !m_bRedBronzeValueRevealed )
+ {
+ m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_bronze", m_nMedalsToAward_Bronze_Blue );
+ m_bBlueBronzeValueRevealed = true;
+ Panel *pChild = m_pBlueMedalsPanel->FindChildByName( "BlueBronzeMedalValue" );
+ if ( pChild )
+ {
+ int nXPos, nYPos, nWide, nTall;
+ pChild->GetBounds( nXPos, nYPos, nWide, nTall );
+ FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Bronze_Blue > 0 ) ? StatMedal_Bronze : StatMedal_None );
+ }
+
+ m_pRedMedalsPanel->SetDialogVariable( "redteammedals_bronze", m_nMedalsToAward_Bronze_Red );
+ m_bRedBronzeValueRevealed = true;
+ pChild = m_pRedMedalsPanel->FindChildByName( "RedBronzeMedalValue" );
+ if ( pChild )
+ {
+ int nXPos, nYPos, nWide, nTall;
+ pChild->GetBounds( nXPos, nYPos, nWide, nTall );
+ FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Bronze_Red > 0 ) ? StatMedal_Bronze : StatMedal_None );
+ }
+
+ m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES;
+ }
+ else
+ {
+ if ( ( nMedalsToAward_Bronze_Total > 0 ) && ( m_nMedalsRevealed < nMedalsToAward_Bronze_Total ) )
+ {
+ Update();
+ m_nMedalsRevealed++;
+ m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS;
+ }
+ else
+ {
+ m_iCurrentState = MS_STATE_SILVER_MEDALS;
+ m_nMedalsRevealed = 0;
+ m_flNextActionTime = MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES;
+ }
+ }
+ }
+ break;
+ }
+ case MS_STATE_SILVER_MEDALS:
+ {
+ if ( gpGlobals->curtime > m_flNextActionTime )
+ {
+ if ( !m_bBlueSilverValueRevealed && !m_bRedSilverValueRevealed )
+ {
+ m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_silver", m_nMedalsToAward_Silver_Blue );
+ m_bBlueSilverValueRevealed = true;
+ Panel *pChild = m_pBlueMedalsPanel->FindChildByName( "BlueSilverMedalValue" );
+ if ( pChild )
+ {
+ int nXPos, nYPos, nWide, nTall;
+ pChild->GetBounds( nXPos, nYPos, nWide, nTall );
+ FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Silver_Blue > 0 ) ? StatMedal_Silver : StatMedal_None );
+ }
+
+ m_pRedMedalsPanel->SetDialogVariable( "redteammedals_silver", m_nMedalsToAward_Silver_Red );
+ m_bRedSilverValueRevealed = true;
+ pChild = m_pRedMedalsPanel->FindChildByName( "RedSilverMedalValue" );
+ if ( pChild )
+ {
+ int nXPos, nYPos, nWide, nTall;
+ pChild->GetBounds( nXPos, nYPos, nWide, nTall );
+ FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Silver_Red > 0 ) ? StatMedal_Silver : StatMedal_None );
+ }
+
+ m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES;
+ }
+ else
+ {
+ if ( ( nMedalsToAward_Silver_Total > 0 ) && ( m_nMedalsRevealed < nMedalsToAward_Silver_Total ) )
+ {
+ Update();
+ m_nMedalsRevealed++;
+ m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS;
+ }
+ else
+ {
+ m_iCurrentState = MS_STATE_GOLD_MEDALS;
+ m_nMedalsRevealed = 0;
+ m_flNextActionTime = MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES;
+ }
+ }
+ }
+ break;
+ }
+ case MS_STATE_GOLD_MEDALS:
+ {
+ if ( gpGlobals->curtime > m_flNextActionTime )
+ {
+ if ( !m_bBlueGoldValueRevealed && !m_bRedGoldValueRevealed )
+ {
+ m_pBlueMedalsPanel->SetDialogVariable( "blueteammedals_gold", m_nMedalsToAward_Gold_Blue );
+ m_bBlueGoldValueRevealed = true;
+ Panel *pChild = m_pBlueMedalsPanel->FindChildByName( "BlueGoldMedalValue" );
+ if ( pChild )
+ {
+ int nXPos, nYPos, nWide, nTall;
+ pChild->GetBounds( nXPos, nYPos, nWide, nTall );
+ FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Gold_Blue > 0 ) ? StatMedal_Gold : StatMedal_None );
+ }
+
+ m_pRedMedalsPanel->SetDialogVariable( "redteammedals_gold", m_nMedalsToAward_Gold_Red );
+ m_bRedGoldValueRevealed = true;
+ pChild = m_pRedMedalsPanel->FindChildByName( "RedGoldMedalValue" );
+ if ( pChild )
+ {
+ int nXPos, nYPos, nWide, nTall;
+ pChild->GetBounds( nXPos, nYPos, nWide, nTall );
+ FireMedalEffects( pChild, nXPos, nYPos, nWide, nTall, ( m_nMedalsToAward_Gold_Red > 0 ) ? StatMedal_Gold : StatMedal_None );
+ }
+
+ m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS_CATEGORIES;
+ }
+ else
+ {
+ if ( ( nMedalsToAward_Gold_Total > 0 ) && ( m_nMedalsRevealed < nMedalsToAward_Gold_Total ) )
+ {
+ Update();
+ m_nMedalsRevealed++;
+ m_flNextActionTime = gpGlobals->curtime + MS_STATE_TIME_BETWEEN_MEDALS;
+ }
+ else
+ {
+ m_iCurrentState = MS_STATE_FINAL;
+ m_nMedalsRevealed = 0;
+ m_flNextActionTime = -1;
+
+ if ( pLocalPlayer )
+ {
+ const char *pszSoundScriptEntry = "Announcer.CompSummaryScreenOutlierNo";
+ if ( nMedalsToAward_Bronze_Total || nMedalsToAward_Silver_Total || nMedalsToAward_Gold_Total )
+ {
+ pszSoundScriptEntry = "Announcer.CompSummaryScreenOutlierYes";
+ }
+
+ const char *pszSoundName = UTIL_GetRandomSoundFromEntry( pszSoundScriptEntry );
+ m_flMedalSoundTime = gpGlobals->curtime + enginesound->GetSoundDuration( pszSoundName ) + 0.5f;
+ pLocalPlayer->EmitSound( pszSoundName );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "ds_screenshot" );
+ if ( event )
+ {
+ event->SetFloat( "delay", 0.5f );
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ default:
+ case MS_STATE_FINAL:
+ {
+ bool bMedalSoundTimeComplete = ( m_flMedalSoundTime > 0 ) && ( m_flMedalSoundTime < gpGlobals->curtime );
+
+ if ( !m_bXPShown /*&& ( !bShowMedals || bMedalSoundTimeComplete ) */)
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "CompetitiveGame_ShowPvPRankPanel", false );
+ m_bXPShown = true;
+ }
+
+ if ( bShowPerformanceMedals )
+ {
+ if ( bMedalSoundTimeComplete )
+ {
+ m_flMedalSoundTime = -1.f;
+ int iLocalPlayerIndex = GetLocalPlayerIndex();
+
+ if ( ( m_SkillRatings[iLocalPlayerIndex].nScoreRank != StatMedal_None ) ||
+ ( m_SkillRatings[iLocalPlayerIndex].nKillsRank != StatMedal_None ) ||
+ ( m_SkillRatings[iLocalPlayerIndex].nDamageRank != StatMedal_None ) ||
+ ( m_SkillRatings[iLocalPlayerIndex].nHealingRank != StatMedal_None ) ||
+ ( m_SkillRatings[iLocalPlayerIndex].nSupportRank != StatMedal_None ) )
+ {
+ if ( pLocalPlayer )
+ {
+ int iClass = RandomInt( TF_CLASS_SCOUT, TF_CLASS_ENGINEER );
+ if ( pLocalPlayer->GetPlayerClass() && ( pLocalPlayer->GetPlayerClass()->GetClassIndex() > TF_CLASS_UNDEFINED ) )
+ {
+ iClass = pLocalPlayer->GetPlayerClass()->GetClassIndex();
+ }
+
+ pLocalPlayer->EmitSound( VarArgs( "%s.CompSummaryScreenOutlier", g_aPlayerClassNames_NonLocalized[iClass] ) );
+ }
+ }
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMatchSummary::LevelInit( void )
+{
+ SetVisible( false );
+}
+
+void CTFMatchSummary::LevelShutdown( void )
+{
+ SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMatchSummary::ShowPerformanceMedals( void )
+{
+ bool bDistributePerformanceMedals = false;
+
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
+ if ( pMatchDesc )
+ {
+ bDistributePerformanceMedals = pMatchDesc->m_params.m_bDistributePerformanceMedals;
+ }
+
+ return ( bDistributePerformanceMedals && !m_bPlayerAbandoned );
+}
diff --git a/game/client/tf/vgui/tf_match_summary.h b/game/client/tf/vgui/tf_match_summary.h
new file mode 100644
index 0000000..80e7a70
--- /dev/null
+++ b/game/client/tf/vgui/tf_match_summary.h
@@ -0,0 +1,248 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_MATCH_SUMMARY_H
+#define TF_MATCH_SUMMARY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "hudelement.h"
+#include <vgui_controls/EditablePanel.h>
+#include <vgui_controls/SectionedListPanel.h>
+#include "tf_imagepanel.h"
+#include "econ_controls.h"
+#include "drawing_panel.h"
+#include "tf_particlepanel.h"
+#include "tf_matchmaking_shared.h"
+#include "tf_gamerules.h"
+#include "tf_match_join_handlers.h"
+
+using namespace vgui;
+
+class CAvatarImagePanel;
+class CTFBadgePanel;
+
+enum matchsummary_displaystate_t
+{
+ MS_STATE_INITIAL = 0,
+ MS_STATE_DRAWING,
+ MS_STATE_STATS,
+ MS_STATE_BRONZE_MEDALS,
+ MS_STATE_SILVER_MEDALS,
+ MS_STATE_GOLD_MEDALS,
+ MS_STATE_FINAL,
+
+ MS_NUM_STATES
+};
+
+enum matchsummary_columns_t
+{
+ MS_COLUMN_INVALID = -1,
+
+ MS_COLUMN_MEDAL = 0,
+ MS_COLUMN_AVATAR,
+ MS_COLUMN_SPACER,
+ MS_COLUMN_NAME,
+ MS_COLUMN_CLASS,
+ MS_COLUMN_SCORE,
+ MS_COLUMN_SCORE_MEDAL,
+ MS_COLUMN_KILLS,
+ MS_COLUMN_KILLS_MEDAL,
+ MS_COLUMN_DAMAGE,
+ MS_COLUMN_DAMAGE_MEDAL,
+ MS_COLUMN_HEALING,
+ MS_COLUMN_HEALING_MEDAL,
+ MS_COLUMN_SUPPORT,
+ MS_COLUMN_SUPPORT_MEDAL,
+
+ MS_NUM_COLUMNS
+};
+
+class TFSectionedListPanel : public vgui::SectionedListPanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( TFSectionedListPanel, vgui::SectionedListPanel );
+
+public:
+ TFSectionedListPanel( Panel *parent, const char *panelName ) : BaseClass( parent, panelName ){}
+ virtual ~TFSectionedListPanel(){}
+
+ CPanelAnimationVarAliasType( int, m_iMedalWidth, "medal_width", "s.05", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAvatarWidth, "avatar_width", "s.1", "proportional_width" ); // Avatar width doesn't scale with resolution
+ CPanelAnimationVarAliasType( int, m_iSpacerWidth, "spacer", "s.1", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iNameWidth, "name_width", "s.1", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iClassWidth, "class_width", "s.1", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAwardWidth, "award_width", "s.1", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iStatsWidth, "stats_width", "s.1", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iHorizFillInset, "horiz_inset", "5", "proportional_int" );
+};
+
+class CTFMatchSummary : public CHudElement, public vgui::EditablePanel
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFMatchSummary, vgui::EditablePanel );
+
+public:
+ CTFMatchSummary( const char *pElementName );
+ virtual ~CTFMatchSummary();
+
+ virtual bool ShouldDraw( void ) OVERRIDE;
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void SetVisible( bool state ) OVERRIDE;
+ virtual void OnTick() OVERRIDE;
+ virtual void LevelInit( void ) OVERRIDE;
+ virtual void LevelShutdown( void ) OVERRIDE;
+
+ virtual GameActionSet_t GetPreferredActionSet() OVERRIDE { return GAME_ACTION_SET_IN_GAME_HUD; }
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+ bool ShowPerformanceMedals( void );
+
+private:
+
+ void Update( void );
+ void InitPlayerList( TFSectionedListPanel *pPlayerList, int nTeam );
+ static bool TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 );
+ void UpdateTeamInfo();
+ void UpdatePlayerList();
+ void UpdatePlayerAvatar( int playerIndex, KeyValues *kv );
+ void RecalculateMedalCounts();
+ void UpdateBadgePanels( CUtlVector<CTFBadgePanel*> &pBadgePanels, TFSectionedListPanel *pPlayerList );
+
+ void InternalUpdateMedalCountForType( int iTeam, StatMedal_t eMedal );
+ matchsummary_columns_t InternalAddMedalKeyValues( int iIndex, StatMedal_t eMedal, KeyValues *pKeyValues, int nTotalMedals = -1 );
+ void FireMedalEffects( Panel *pPanel, int nPanelXPos, int nPanelYPos, int nPanelWide, int nPanelTall, StatMedal_t eParticleMedal );
+
+private:
+ EditablePanel *m_pTeamScoresPanel;
+
+ int m_iImageClass[SCOREBOARD_CLASS_ICONS];
+ int m_iImageClassAlt[SCOREBOARD_CLASS_ICONS];
+ int m_iImageMedals[StatMedal_Max];
+
+ CDrawingPanel *m_pDrawingPanel;
+
+ vgui::EditablePanel *m_pBlueTeamPanel;
+ vgui::EditablePanel *m_pRedTeamPanel;
+
+ vgui::EditablePanel *m_pMainStatsContainer;
+ vgui::EditablePanel *m_pPlayerListBlueParent;
+ TFSectionedListPanel *m_pPlayerListBlue;
+ vgui::EditablePanel *m_pPlayerListRedParent;
+ TFSectionedListPanel *m_pPlayerListRed;
+ CExLabel *m_pBlueTeamScore;
+ CExLabel *m_pBlueTeamScoreDropshadow;
+ EditablePanel *m_pBlueTeamScoreBG;
+ EditablePanel *m_pBluePlayerListBG;
+ CExLabel *m_pRedTeamScore;
+ CExLabel *m_pRedTeamScoreDropshadow;
+ EditablePanel *m_pRedTeamScoreBG;
+ EditablePanel *m_pRedPlayerListBG;
+ EditablePanel *m_pBlueMedalsPanel;
+ EditablePanel *m_pRedMedalsPanel;
+ vgui::ImagePanel *m_pRedTeamImage;
+ vgui::ImagePanel *m_pBlueTeamImage;
+ CAvatarImagePanel *m_pRedLeaderAvatarImage;
+ CAvatarImagePanel *m_pBlueLeaderAvatarImage;
+ EditablePanel *m_pRedLeaderAvatarBG;
+ EditablePanel *m_pBlueLeaderAvatarBG;
+ EditablePanel *m_pStatsLabelPanel;
+ CExLabel *m_pStatsAndMedals;
+ CExLabel *m_pStatsAndMedalsShadow;
+ CExLabel *m_pBlueTeamName;
+ CExLabel *m_pRedTeamName;
+ CExLabel *m_pRedTeamWinner;
+ CExLabel *m_pRedTeamWinnerDropshadow;
+ CExLabel *m_pBlueTeamWinner;
+ CExLabel *m_pBlueTeamWinnerDropshadow;
+
+ CTFParticlePanel *m_pParticlePanel;
+
+ vgui::EditablePanel *m_pStatsBgPanel;
+
+ vgui::ImageList *m_pImageList;
+ CUtlMap<CSteamID,int> m_mapAvatarsToImageList;
+
+ vgui::HFont m_hFont;
+ bool m_bLargeMatchGroup;
+ bool m_bXPShown;
+
+ float m_flDrawingPanelTime;
+
+ CPanelAnimationVar( Color, m_clrGoldMedal, "GoldMedalText", "214 186 24 255" );
+ CPanelAnimationVar( Color, m_clrSilverMedal, "SilverMedalText", "222 218 222 255" );
+ CPanelAnimationVar( Color, m_clrBronzeMedal, "BronzeMedalText", "214 125 57 255" );
+
+ CPanelAnimationVarAliasType( int, m_iAnimBluePlayerListParent, "AnimBluePlayerListParent", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimBlueTeamScore, "AnimBlueTeamScore", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimBlueTeamScoreDropshadow, "AnimBlueTeamScoreDropshadow", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimBlueTeamScoreBG, "AnimBlueTeamScoreBG", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimBluePlayerListBG, "AnimBluePlayerListBG", "0", "proportional_width" );
+
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreBGWide, "AnimRedTeamScoreBGWide", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreBGXPos, "AnimRedTeamScoreBGXPos", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreWide, "AnimRedTeamScoreWide", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreXPos, "AnimRedTeamScoreXPos", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreDropshadowWide, "AnimRedTeamScoreDropshadowWide", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamScoreDropshadowXPos, "AnimRedTeamScoreDropshadowXPos", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListParentWide, "AnimRedPlayerListParentWide", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListParentXPos, "AnimRedPlayerListParentXPos", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListBGWide, "AnimRedPlayerListBGWide", "0", "proportional_width" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedPlayerListBGXPos, "AnimRedPlayerListBGXPos", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iAnimBlueMedalsYPos, "AnimBlueMedalsYPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedMedalsYPos, "AnimRedMedalsYPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimStatsLabelPanel6v6YPos, "AnimStatsLabelPanel6v6YPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimBlueTeamLabel6v6YPos, "AnimBlueTeamLabel6v6YPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamLabel6v6YPos, "AnimRedTeamLabel6v6YPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimStatsLabelPanel12v12YPos, "AnimStatsLabelPanel12v12YPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimBlueTeamLabel12v12YPos, "AnimBlueTeamLabel12v12YPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimRedTeamLabel12v12YPos, "AnimRedTeamLabel12v12YPos", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iAnimStatsContainer12v12YPos, "AnimStatsContainer12v12YPos", "0", "proportional_ypos" );
+
+ struct MatchDataUpdate_t
+ {
+ uint32 unRating;
+ int nDelta;
+ int nScoreRank;
+ int nKillsRank;
+ int nDamageRank;
+ int nHealingRank;
+ int nSupportRank;
+ };
+ MatchDataUpdate_t m_SkillRatings[MAX_PLAYERS + 1];
+
+ int m_iCurrentState;
+ float m_flNextActionTime;
+
+ int m_nMedalsToAward_Bronze_Blue;
+ int m_nMedalsToAward_Silver_Blue;
+ int m_nMedalsToAward_Gold_Blue;
+ int m_nMedalsToAward_Bronze_Red;
+ int m_nMedalsToAward_Silver_Red;
+ int m_nMedalsToAward_Gold_Red;
+
+ int m_nMedalsRevealed;
+
+ int m_nNumMedalsThisUpdate;
+
+ bool m_bBlueGoldValueRevealed;
+ bool m_bBlueSilverValueRevealed;
+ bool m_bBlueBronzeValueRevealed;
+ bool m_bRedGoldValueRevealed;
+ bool m_bRedSilverValueRevealed;
+ bool m_bRedBronzeValueRevealed;
+ bool m_bPlayerAbandoned;
+
+ float m_flMedalSoundTime;
+
+ CUtlVector< CTFBadgePanel* > m_pBlueBadgePanels;
+ CUtlVector< CTFBadgePanel* > m_pRedBadgePanels;
+};
+
+#endif //TF_MATCH_SUMMARY_H
diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard.cpp
new file mode 100644
index 0000000..ca10e8b
--- /dev/null
+++ b/game/client/tf/vgui/tf_matchmaking_dashboard.cpp
@@ -0,0 +1,594 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "tf_shareddefs.h"
+#include "tf_matchmaking_dashboard.h"
+#include "tf_gamerules.h"
+#include "ienginevgui.h"
+#include "clientmode_tf.h"
+#include "tf_hud_disconnect_prompt.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+#include "../vgui2/src/VPanel.h"
+
+using namespace vgui;
+using namespace GCSDK;
+
+ConVar tf_mm_dashboard_spew_enabled( "tf_mm_dashboard_spew_enabled", "0", FCVAR_ARCHIVE );
+#define MMDashboardSpew(...) \
+ do { \
+ if ( tf_mm_dashboard_spew_enabled.GetBool() ) \
+ { \
+ ConColorMsg( Color( 187, 80, 255, 255 ), "MMDashboard:" __VA_ARGS__ ); \
+ } \
+ } while(false) \
+
+extern ConVar tf_mm_next_map_vote_time;
+
+#ifdef STAGING_ONLY
+ConVar tf_mm_dashboard_force_show( "tf_mm_dashboard_force_show", "0", 0, "Force the mm dashboard to show" );
+ConVar tf_mm_popup_state_override( "tf_mm_popup_state_override", "", 0, "Force state on mm dashboard popup" );
+#endif
+
+
+
+bool BInEndOfMatch()
+{
+ const bool bInEndOfMatch = TFGameRules() &&
+ TFGameRules()->State_Get() == GR_STATE_GAME_OVER &&
+ GTFGCClientSystem()->BConnectedToMatchServer( false );
+
+ return bInEndOfMatch;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pnael that lives on the viewport that is a popup that we parent
+// the MM dashboard panels to
+//-----------------------------------------------------------------------------
+class CMatchMakingHUDPopupContainer : public Panel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CMatchMakingHUDPopupContainer, Panel );
+ CMatchMakingHUDPopupContainer()
+ : Panel( g_pClientMode->GetViewport(), "MMDashboardPopupContainer" )
+ {
+ SetProportional( true );
+ SetBounds( 0, 0, g_pClientMode->GetViewport()->GetWide(), g_pClientMode->GetViewport()->GetTall() );
+ MakePopup();
+ SetMouseInputEnabled( true );
+ SetKeyBoardInputEnabled( false ); // This can never be true
+ SetVisible( false );
+ ivgui()->AddTickSignal( GetVPanel(), 100 );
+ }
+
+ virtual void OnTick()
+ {
+ BaseClass::OnThink();
+
+ bool bChildrenVisible = false;
+ int nCount = GetChildCount();
+ for( int i=0; i < nCount && !bChildrenVisible; ++i )
+ {
+ CExpandablePanel* pChild = assert_cast< CExpandablePanel* >( GetChild( i ) );
+ bChildrenVisible = bChildrenVisible || pChild->BIsExpanded() || ( !pChild->BIsExpanded() && pChild->GetPercentAnimated() != 1.f );
+ }
+
+ SetVisible( bChildrenVisible );
+ }
+};
+
+CMMDashboardParentManager::CMMDashboardParentManager()
+ : m_bAttachedToGameUI( false )
+{
+ ListenForGameEvent( "gameui_activated" );
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_pHUDPopup = new CMatchMakingHUDPopupContainer();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update who we need to parent the MM dashboard panels to
+//-----------------------------------------------------------------------------
+void CMMDashboardParentManager::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "gameui_activated" ) )
+ {
+ m_bAttachedToGameUI = false;
+ }
+ else if ( FStrEq( event->GetName(), "gameui_hidden" ) )
+ {
+ m_bAttachedToGameUI = true;
+ }
+
+ UpdateParenting();
+}
+
+void CMMDashboardParentManager::AddPanel( CExpandablePanel* pPanel )
+{
+ m_vecPanels.Insert( pPanel );
+ UpdateParenting();
+}
+
+void CMMDashboardParentManager::RemovePanel( CExpandablePanel* pPanel )
+{
+ m_vecPanels.FindAndRemove( pPanel );
+ m_vecPanels.RedoSort();
+ UpdateParenting();
+}
+
+void CMMDashboardParentManager::PushModalFullscreenPopup( vgui::Panel* pPanel )
+{
+ m_vecFullscreenPopups.AddToTail( pPanel );
+ UpdateParenting();
+}
+
+void CMMDashboardParentManager::PopModalFullscreenPopup( vgui::Panel* pPanel )
+{
+ m_vecFullscreenPopups.FindAndRemove( pPanel );
+ UpdateParenting();
+}
+
+void CMMDashboardParentManager::UpdateParenting()
+{
+ m_bAttachedToGameUI ? AttachToGameUI() : AttachToTopMostPopup();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Parent the MM dashboard panels to the right panels
+//-----------------------------------------------------------------------------
+void CMMDashboardParentManager::AttachToGameUI()
+{
+ if ( !m_pHUDPopup )
+ {
+ return;
+ }
+
+ FOR_EACH_VEC( m_vecPanels, i )
+ {
+ CExpandablePanel *pPanel = m_vecPanels[ i ];
+ bool bKBInput = pPanel->IsKeyBoardInputEnabled();
+ bool bMouseInput = pPanel->IsMouseInputEnabled();
+
+ pPanel->SetParent( (Panel*)m_pHUDPopup );
+
+ // Restore mouse, KV input sensitivity because MakePopup forces both to true
+ pPanel->SetKeyBoardInputEnabled( bKBInput );
+ pPanel->SetMouseInputEnabled( bMouseInput );
+ // Don't adopt the parent's proportionalness
+ pPanel->SetProportional( true );
+ }
+}
+
+void CMMDashboardParentManager::AttachToTopMostPopup()
+{
+ // Not being used. Hide it.
+ if ( m_pHUDPopup )
+ {
+ m_pHUDPopup->SetVisible( false );
+ }
+
+ FOR_EACH_VEC( m_vecPanels, i )
+ {
+ Panel *pPanel = m_vecPanels[ i ];
+ // No longer a popup
+ surface()->ReleasePanel( pPanel->GetVPanel() );
+ ((VPanel*)pPanel->GetVPanel())->SetPopup( false );
+
+ if ( m_vecFullscreenPopups.Count() )
+ {
+ pPanel->SetParent( m_vecFullscreenPopups.Tail() );
+ }
+ else
+ {
+ pPanel->SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) );
+ }
+ pPanel->MoveToFront();
+ // Don't adopt the parent's proportionalness
+ pPanel->SetProportional( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Snag the singleton CMMDashboardParentManager
+//-----------------------------------------------------------------------------
+CMMDashboardParentManager* GetMMDashboardParentManager()
+{
+ static CMMDashboardParentManager* s_pParentManager = NULL;
+ if ( !s_pParentManager )
+ {
+ s_pParentManager = new CMMDashboardParentManager();
+ }
+
+ return s_pParentManager;
+}
+
+
+void CTFMatchmakingPopup::OnEnter()
+{
+ MMDashboardSpew( "Entering state %s\n", GetName() );
+
+ Update();
+
+ SetCollapsed( false );
+}
+
+void CTFMatchmakingPopup::OnUpdate()
+{
+}
+
+void CTFMatchmakingPopup::OnExit()
+{
+ MMDashboardSpew( "Exiting state %s\n", GetName() );
+ SetCollapsed( true );
+}
+
+
+void CTFMatchmakingPopup::Update()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMatchmakingPopup::CTFMatchmakingPopup( const char* pszName, const char* pszResFile )
+ : CExpandablePanel( NULL, pszName )
+ , m_pszResFile( pszResFile )
+ , m_bActive( false )
+{
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ ivgui()->AddTickSignal( GetVPanel(), 100 );
+
+ GetMMDashboardParentManager()->AddPanel( this );
+ SetKeyBoardInputEnabled( false );
+
+ SetProportional( true );
+
+ ListenForGameEvent( "rematch_failed_to_create" );
+ ListenForGameEvent( "party_updated" );
+}
+
+CTFMatchmakingPopup::~CTFMatchmakingPopup()
+{
+ GetMMDashboardParentManager()->RemovePanel( this );
+}
+
+void CTFMatchmakingPopup::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetMouseInputEnabled( true );
+ LoadControlSettings( m_pszResFile );
+
+ // This cannot ever be true or else things get weird when in-game
+ SetKeyBoardInputEnabled( false );
+
+ if ( m_bActive )
+ {
+ OnEnter();
+ }
+ else
+ {
+ OnExit();
+ }
+
+ GetMMDashboardParentManager()->UpdateParenting();
+}
+
+void CTFMatchmakingPopup::OnThink()
+{
+ BaseClass::OnThink();
+
+ // Move us to be touching the bottom of the dashboard panel
+ // These panels have no relation whatsoever, so we're doing this manually
+ Panel* pDashboard = GetMMDashboard();
+ int nNewYPos = Max( pDashboard->GetYPos() + pDashboard->GetTall() - YRES(10), YRES(-5) );
+ SetPos( GetXPos(), nNewYPos );
+
+ if ( m_bActive )
+ {
+ OnUpdate();
+ }
+}
+
+void CTFMatchmakingPopup::OnTick()
+{
+ BaseClass::OnTick();
+
+ bool bShouldBeActive = ShouldBeActve();
+ if ( bShouldBeActive != m_bActive )
+ {
+ if ( bShouldBeActive )
+ {
+ m_bActive = true;
+ OnEnter();
+ }
+ else
+ {
+ m_bActive = false;
+ OnExit();
+ }
+ }
+
+ SetMouseInputEnabled( ShouldBeActve() );
+ SetKeyBoardInputEnabled( false ); // Never
+}
+
+
+void CTFMatchmakingPopup::OnCommand( const char *command )
+{
+ if ( FStrEq( "join_match", command ) )
+ {
+ JoinMatch();
+ }
+ else if ( FStrEq( "abandon_match", command ) )
+ {
+ GTFGCClientSystem()->RejoinLobby( false );
+ }
+ else if ( FStrEq( "leave_queue", command ) )
+ {
+ // Only the leader can leave the queue
+ if ( GTFGCClientSystem()->BIsPartyLeader() )
+ {
+ switch( GTFGCClientSystem()->GetSearchMode() )
+ {
+ case TF_Matchmaking_LADDER:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
+ break;
+
+ case TF_Matchmaking_CASUAL:
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL );
+ break;
+
+ default:
+ // Unhandled
+ Assert( false );
+ break;
+ };
+ }
+ }
+ else if( FStrEq( "rematch", command ) )
+ {
+ if ( !GTFGCClientSystem()->BIsPartyLeader() )
+ return;
+
+ engine->ClientCmd( "rematch_vote 2" );
+ }
+ else if ( FStrEq( "new_match", command ) )
+ {
+ if ( !GTFGCClientSystem()->BIsPartyLeader() )
+ return;
+
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING );
+ engine->ClientCmd( "rematch_vote 1" );
+ }
+ else if ( FStrEq( "leave_party", command ) )
+ {
+ // Leave current party and create a new one!
+ GTFGCClientSystem()->SendExitMatchmaking( false );
+ return;
+ }
+}
+
+
+void CTFMatchmakingPopup::FireGameEvent( IGameEvent *pEvent )
+{
+ if ( FStrEq( pEvent->GetName(), "rematch_failed_to_create" ) )
+ {
+ // If the GC failed to create our rematch, then go ahead and requeue
+ if ( !GTFGCClientSystem()->BIsPartyLeader() )
+ return;
+
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING );
+ }
+ else if ( FStrEq( pEvent->GetName(), "party_updated" ) )
+ {
+ if ( ShouldBeActve() )
+ {
+ Update();
+ }
+ }
+}
+
+
+#ifdef STAGING_ONLY
+CON_COMMAND( reload_mm_popup, "Reload the mm popup panel. Pass any 2nd argument to recreate the panel." )
+{
+ bool bRecreate = false;
+ if ( args.ArgC() == 2 )
+ {
+ bRecreate = true;
+ }
+
+ auto& vecPopups = CreateMMPopupPanels( bRecreate );
+
+ if ( !bRecreate )
+ {
+ FOR_EACH_VEC( vecPopups, i )
+ {
+ vecPopups[ i ]->InvalidateLayout( true, true );
+ }
+ }
+}
+
+CON_COMMAND( reload_mm_dashboard, "Reload the mm join panel." )
+{
+ GetMMDashboard()->InvalidateLayout( true, true );
+}
+#endif
+
+
+CTFMatchmakingDashboard::CTFMatchmakingDashboard()
+ : CExpandablePanel( NULL, "MMDashboard" )
+{
+ SetKeyBoardInputEnabled( false );
+ ivgui()->AddTickSignal( GetVPanel(), 100 );
+
+ GetMMDashboardParentManager()->AddPanel( this );
+
+ CreateMMPopupPanels();
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+}
+
+CTFMatchmakingDashboard::~CTFMatchmakingDashboard()
+{
+ GetMMDashboardParentManager()->RemovePanel( this );
+}
+
+void CTFMatchmakingDashboard::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetMouseInputEnabled( true );
+ LoadControlSettings( "resource/UI/MatchMakingDashboard.res" );
+
+ // This cannot ever be true or else things get weird when in-game
+ SetKeyBoardInputEnabled( false );
+
+ GetMMDashboardParentManager()->UpdateParenting();
+}
+
+void CTFMatchmakingDashboard::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "disconnect" ) )
+ {
+ CTFParty* pParty = GTFGCClientSystem()->GetParty();
+ bool bInPartyOfMany = pParty && pParty->GetNumMembers() > 1;
+
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
+ if ( pMatchDesc && pMatchDesc->BShouldAutomaticallyRequeueOnMatchEnd() && !bInPartyOfMany )
+ {
+ // If this is an auto-requeue match type, then assume hitting the "Disconnect" button
+ // means you are done playing entirely with MM. Close out to the main menu.
+ bool bNoPenalty = ( GTFGCClientSystem()->GetAssignedMatchAbandonStatus() != k_EAbandonGameStatus_AbandonWithPenalty );
+ if ( bNoPenalty )
+ {
+ GTFGCClientSystem()->EndMatchmaking( true );
+ }
+ else
+ {
+ // Prompt if this would be considered an abandon with a penalty
+ CTFDisconnectConfirmDialog *pDialog = BuildDisconnectConfirmDialog();
+ if ( pDialog )
+ {
+ pDialog->Show();
+ }
+ }
+ }
+ else
+ {
+ // Go back to the MM screens. The party leader will request the right wizard state
+ switch( GTFGCClientSystem()->GetSearchMode() )
+ {
+ case TF_Matchmaking_LADDER:
+ if ( GTFGCClientSystem()->BIsPartyLeader() )
+ {
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
+ }
+ engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
+ break;
+
+ case TF_Matchmaking_CASUAL:
+ if ( GTFGCClientSystem()->BIsPartyLeader() )
+ {
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL );
+ }
+ engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby casual" );
+ break;
+
+ default:
+ // Unhandled
+ GTFGCClientSystem()->EndMatchmaking();
+ Assert( false );
+ break;
+ };
+ }
+
+ // Disconnect from the server
+ engine->DisconnectInternal();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Figure out if we should be visible and require mouse input
+//-----------------------------------------------------------------------------
+void CTFMatchmakingDashboard::OnTick()
+{
+ bool bInEndOfMatch = TFGameRules() && TFGameRules()->State_Get() == GR_STATE_GAME_OVER;
+
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules() ? TFGameRules()->GetCurrentMatchGroup() : k_nMatchGroup_Invalid );
+ bool bShouldBeVisible = GTFGCClientSystem()->BConnectedToMatchServer( false )
+ && bInEndOfMatch
+ && pMatchDesc
+ && pMatchDesc->BUsesDashboard();
+
+#ifdef STAGING_ONLY
+ bShouldBeVisible |= tf_mm_dashboard_force_show.GetBool();
+#endif
+
+ if ( BIsExpanded() && !bShouldBeVisible )
+ {
+ SetCollapsed( true );
+ }
+ else if ( !BIsExpanded() && bShouldBeVisible )
+ {
+ SetCollapsed( false );
+ }
+
+ SetKeyBoardInputEnabled( false );
+ SetMouseInputEnabled( BIsExpanded() );
+}
+
+CUtlVector< IMMPopupFactory* > IMMPopupFactory::s_vecPopupFactories;
+
+//-----------------------------------------------------------------------------
+// Purpose: Snag the singleton CTFMatchmakingPopup
+//-----------------------------------------------------------------------------
+CUtlVector< CTFMatchmakingPopup* >& CreateMMPopupPanels( bool bRecreate /*= false*/ )
+{
+ static CUtlVector< CTFMatchmakingPopup* > s_vecPopups;
+
+ if ( bRecreate && s_vecPopups.Count() )
+ {
+ FOR_EACH_VEC( s_vecPopups, i )
+ {
+ s_vecPopups[ i ]->MarkForDeletion();
+ }
+ s_vecPopups.Purge();
+ }
+
+
+ if ( s_vecPopups.IsEmpty() )
+ {
+ FOR_EACH_VEC( IMMPopupFactory::s_vecPopupFactories, i )
+ {
+ s_vecPopups.AddToTail( IMMPopupFactory::s_vecPopupFactories[i]->Create() );
+ }
+ }
+
+ return s_vecPopups;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Snag the singleton CTFMatchmakingDashboard
+//-----------------------------------------------------------------------------
+CTFMatchmakingDashboard* GetMMDashboard()
+{
+ static CTFMatchmakingDashboard* s_pDashboardPanel = NULL;
+ if ( !s_pDashboardPanel )
+ {
+ s_pDashboardPanel = new CTFMatchmakingDashboard();
+ }
+
+ return s_pDashboardPanel;
+}
diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard.h b/game/client/tf/vgui/tf_matchmaking_dashboard.h
new file mode 100644
index 0000000..57a54e1
--- /dev/null
+++ b/game/client/tf/vgui/tf_matchmaking_dashboard.h
@@ -0,0 +1,158 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_MATCHMAKING_DASHBOARD_H
+#define TF_MATCHMAKING_DASHBOARD_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_match_join_handlers.h"
+#include <vgui_controls/EditablePanel.h>
+#include "tf_controls.h"
+#include <vgui_controls/PHandle.h>
+#include "local_steam_shared_object_listener.h"
+
+CUtlVector< class CTFMatchmakingPopup* >& CreateMMPopupPanels( bool bRecreate = false );
+class CTFMatchmakingDashboard* GetMMDashboard();
+class CMMDashboardParentManager* GetMMDashboardParentManager();
+
+bool BInEndOfMatch();
+
+//-----------------------------------------------------------------------------
+// Purpose: Popup that goes underneath the dashboard and displays anything
+// important the user needs to know about
+//-----------------------------------------------------------------------------
+class CTFMatchmakingPopup : public CExpandablePanel
+ , public CGameEventListener
+ , public IMatchJoiningHandler
+{
+ friend class CTFMatchmakingPopupState;
+ friend class CTFMatchmakingDashboard;
+ DECLARE_CLASS_SIMPLE( CTFMatchmakingPopup, CExpandablePanel );
+public:
+
+ CTFMatchmakingPopup( const char* pszName, const char* pszResFile );
+ virtual ~CTFMatchmakingPopup();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void OnTick() OVERRIDE;
+
+ virtual void OnEnter();
+ virtual void OnUpdate();
+ virtual void OnExit();
+ virtual void Update();
+
+ virtual void FireGameEvent( IGameEvent *pEvent ) OVERRIDE;
+
+private:
+
+ virtual void MatchFound() {} // We dont need to do anything special
+ virtual bool ShouldBeActve() const = 0;
+ void UpdateRematchtime();
+ void UpdateAutoJoinTime();
+
+ bool m_bActive;
+ const char* m_pszResFile;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Matchmaking panel that contains controls for matchmaking
+//-----------------------------------------------------------------------------
+class CTFMatchmakingDashboard : public CExpandablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CTFMatchmakingDashboard, CExpandablePanel );
+ CTFMatchmakingDashboard();
+ virtual ~CTFMatchmakingDashboard();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void OnTick() OVERRIDE;
+};
+
+//-----------------------------------------------------------------------------
+// CMMDashboardParentManager
+// Purpose: This guy keeps the MM dashboard as the top-most panel but does so
+// *without making it a popup*. This is important because popups look
+// awful whenever they overlap and transparency is involved. This class
+// does its dirty work by keeping track of the top-most fullscreen popup
+// and setting that panel as the MM dashboard's parent. When that popup
+// goes away, we set the parent to the next popup on the stack, or to
+// the GameUI if none are active. If we're in-game, then we parent to
+// the our special popup container. Why not always just parent to that
+// single popup container? Because we want the MINIMUM mouse focus area
+// possible because the dashboard is not a rectangle (it grows/shrinks).
+//
+//
+// If anything draws on top of the MM dashboard and you dont want it to
+// have that panel add itself to this class using PushModalFullscreenPopup
+// when it goes visible and PopModalFullscreenPopup when it hides itself
+//-----------------------------------------------------------------------------
+class CMMDashboardParentManager : public CGameEventListener
+{
+public:
+ friend class CTFMatchmakingDashboard;
+ friend class CTFMatchmakingPopup;
+
+ CMMDashboardParentManager();
+
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+ void PushModalFullscreenPopup( vgui::Panel* pPanel );
+ void PopModalFullscreenPopup( vgui::Panel* pPanel );
+ void UpdateParenting();
+private:
+
+ void AddPanel( CExpandablePanel* pPanel );
+ void RemovePanel( CExpandablePanel* pPanel );
+
+ void AttachToGameUI();
+ void AttachToTopMostPopup();
+
+ bool m_bAttachedToGameUI;
+
+ class CUtlSortVectorPanelZPos
+ {
+ public:
+ bool Less( const vgui::Panel* lhs, const vgui::Panel* rhs, void * )
+ {
+ return lhs->GetZPos() < rhs->GetZPos();
+ }
+ };
+
+ CUtlSortVector< CExpandablePanel*, CUtlSortVectorPanelZPos > m_vecPanels;
+ CUtlVector< vgui::Panel* > m_vecFullscreenPopups;
+
+ vgui::PHandle m_pHUDPopup;
+};
+
+class IMMPopupFactory
+{
+public:
+ virtual CTFMatchmakingPopup* Create() const = 0;
+ static CUtlVector< IMMPopupFactory* > s_vecPopupFactories;
+};
+
+template< typename Type >
+class CMMPopupFactoryImplementation : public IMMPopupFactory
+{
+public:
+ CMMPopupFactoryImplementation( const char* pszName, const char* pszResFile ) : m_pszName( pszName ), m_pszResFile( pszResFile )
+ { s_vecPopupFactories.AddToTail( this ); }
+
+ virtual CTFMatchmakingPopup* Create() const OVERRIDE { return new Type( m_pszName, m_pszResFile ); }
+private:
+ const char* m_pszName;
+ const char* m_pszResFile;
+};
+
+#define REG_MM_POPUP_FACTORY( type, name, resfile ) CMMPopupFactoryImplementation< type > g_##type##Factory( name, resfile );
+
+#endif // TF_MATCHMAKING_DASHBOARD_H
diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard_new_match_found.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard_new_match_found.cpp
new file mode 100644
index 0000000..b280a21
--- /dev/null
+++ b/game/client/tf/vgui/tf_matchmaking_dashboard_new_match_found.cpp
@@ -0,0 +1,99 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "tf_matchmaking_dashboard.h"
+#include "tf_gc_client.h"
+#include "tf_gamerules.h"
+
+using namespace vgui;
+using namespace GCSDK;
+
+#ifdef STAGING_ONLY
+extern ConVar tf_mm_popup_state_override;
+#endif
+
+class CNewMatchFoundDashboardState : public CTFMatchmakingPopup
+{
+public:
+ CNewMatchFoundDashboardState( const char* pszName, const char* pszResFile )
+ : CTFMatchmakingPopup( pszName, pszResFile )
+ , m_flAutoJoinTime( 0.f )
+ {}
+
+ virtual void OnEnter() OVERRIDE
+ {
+ CTFMatchmakingPopup::OnEnter();
+
+ // We want to coincide the auto-join time with a short pause after the match-summary sequence if we're in
+ // a match.
+ if ( GTFGCClientSystem()->BConnectedToMatchServer( false ) )
+ {
+ Assert( TFGameRules()->State_Get() == GR_STATE_GAME_OVER || TFGameRules()->State_Get() == GR_STATE_TEAM_WIN );
+
+ const double flNow = gpGlobals->curtime;
+
+ // There's a point that's the earliest we want to autojoin and also a point that's the latest we want to autojoin.
+ const double flDesiredAutoJoinTime = flNow + 10.;
+ const double flLatestJoinTime = TFGameRules()->State_Get() == GR_STATE_GAME_OVER ? TFGameRules()->GetStateTransitionTime() - 1. : TFGameRules()->GetStateTransitionTime() + TFGameRules()->GetPostMatchPeriod();
+ const double flEarliestJoinTime = TFGameRules()->State_Get() == GR_STATE_GAME_OVER ? 0. : TFGameRules()->GetStateTransitionTime() + 10.;
+
+ // We also want the minimum time of the autojoin to be 10 seconds long. If the earliest join time is greater than
+ // the now + 10, go with that. If now + 10 is beyond the latest join time, go with the latest join time.
+ m_flAutoJoinTime = Max( flEarliestJoinTime, flDesiredAutoJoinTime );
+ m_flAutoJoinTime = Min( m_flAutoJoinTime, flLatestJoinTime );
+ }
+ }
+
+ virtual void OnUpdate() OVERRIDE
+ {
+ CTFMatchmakingPopup::OnUpdate();
+
+ // Autojoin time
+ wchar_t *pwszAutoJoinTime = NULL;
+
+ // Update the countdown label
+ double flTimeUntilAutoJoin = Max( 0., m_flAutoJoinTime - gpGlobals->curtime );
+ pwszAutoJoinTime = LocalizeNumberWithToken( "TF_Matchmaking_RollingQueue_AutojoinWarning", ceil( flTimeUntilAutoJoin ) );
+
+ SetDialogVariable( "auto_join", pwszAutoJoinTime );
+
+
+ if ( m_flAutoJoinTime != 0.f && gpGlobals->curtime > m_flAutoJoinTime )
+ {
+ GTFGCClientSystem()->JoinMMMatch();
+ }
+ }
+
+ virtual void OnExit() OVERRIDE
+ {
+ CTFMatchmakingPopup::OnExit();
+
+ // No more auto join timer
+ m_flAutoJoinTime = 0.f;
+ }
+
+ virtual bool ShouldBeActve() const OVERRIDE
+ {
+#ifdef STAGING_ONLY
+ if ( FStrEq( const_cast<CNewMatchFoundDashboardState*>(this)->GetName(), tf_mm_popup_state_override.GetString() ) )
+ return true;
+#endif
+
+ if ( BInEndOfMatch() && !GTFGCClientSystem()->BConnectedToMatchServer( true ) && GTFGCClientSystem()->BHaveLiveMatch() )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ double m_flAutoJoinTime;
+};
+
+REG_MM_POPUP_FACTORY( CNewMatchFoundDashboardState, "NewMatchFound", "resource/UI/MatchMakingDashboardPopup_NewMatch.res" ) \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_voting.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_voting.cpp
new file mode 100644
index 0000000..e8b86ac
--- /dev/null
+++ b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_voting.cpp
@@ -0,0 +1,293 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "tf_matchmaking_dashboard.h"
+#include "tf_gamerules.h"
+#include "tf_gc_client.h"
+#include "clientmode_tf.h"
+#include <vgui_controls/AnimationController.h>
+#include <vgui_controls/CircularProgressBar.h>
+
+using namespace vgui;
+using namespace GCSDK;
+
+extern ConVar tf_mm_next_map_vote_time;
+
+#ifdef STAGING_ONLY
+extern ConVar tf_mm_popup_state_override;
+#endif
+
+#ifdef STAGING_ONLY
+CON_COMMAND( test_next_map_vote, "Fakes a player voting" )
+{
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_next_map_vote_change" );
+ if ( event )
+ {
+ event->SetInt( "map_index", RandomInt( 0, 2 ) );
+ // Client-side once it's actually happened
+ gameeventmanager->FireEventClientSide( event );
+ }
+}
+#endif
+
+class CNextMapVotingDashboardState : public CTFMatchmakingPopup
+{
+public:
+ CNextMapVotingDashboardState( const char* pszName, const char* pszResFile )
+ : CTFMatchmakingPopup( pszName, pszResFile )
+ , m_pTimerProgressBar( NULL )
+ {
+ memset( m_arMapPanels, 0, sizeof( m_arMapPanels ) );
+ ListenForGameEvent( "player_next_map_vote_change" );
+ ListenForGameEvent( "vote_maps_changed" );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ CTFMatchmakingPopup::ApplySchemeSettings( pScheme );
+
+ m_pTimerProgressBar = FindControl< CircularProgressBar >( "TimeRemainingProgressBar", true );
+ if ( m_pTimerProgressBar )
+ {
+ m_pTimerProgressBar->SetProgressDirection( CircularProgressBar::PROGRESS_CCW );
+ m_pTimerProgressBar->SetFgImage( GetLocalPlayerTeam() == TF_TEAM_RED ? "progress_bar_red" : "progress_bar_blu" );
+ }
+
+ for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i )
+ {
+ EditablePanel* pMapChoice = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", i ), true );
+ if ( pMapChoice )
+ {
+ pMapChoice->LoadControlSettings( "resource/UI/MatchMakingDashboardPopup_MapVotePanel.res" );
+ }
+ }
+ }
+
+ virtual void PerformLayout() OVERRIDE
+ {
+ CTFMatchmakingPopup::PerformLayout();
+
+ SetMapChoiceSettings();
+ UpdateVoteCounts();
+ }
+
+ virtual void OnUpdate() OVERRIDE
+ {
+ CTFMatchmakingPopup::OnUpdate();
+
+ // Default to looping 30 sec cycle for debugging
+ float flVoteEndTime = ( 30 + ( ( int( Plat_FloatTime() ) / 30 ) * 30 ) - Plat_FloatTime() ) / 30.f;
+
+ if ( TFGameRules() )
+ {
+ // Get the actual countdown if we have gamerules
+ flVoteEndTime = ( tf_mm_next_map_vote_time.GetInt() - ( gpGlobals->curtime - TFGameRules()->GetLastRoundStateChangeTime() ) ) / tf_mm_next_map_vote_time.GetFloat();
+ }
+
+ if ( m_pTimerProgressBar )
+ {
+ m_pTimerProgressBar->SetProgress( flVoteEndTime );
+ }
+ }
+
+ virtual bool ShouldBeActve() const OVERRIDE
+ {
+#ifdef STAGING_ONLY
+ if ( FStrEq( const_cast<CNextMapVotingDashboardState*>(this)->GetName(), tf_mm_popup_state_override.GetString() ) )
+ return true;
+#endif
+
+ if ( BInEndOfMatch() &&
+ TFGameRules() &&
+ TFGameRules()->GetCurrentNextMapVotingState() == CTFGameRules::NEXT_MAP_VOTE_STATE_WAITING_FOR_USERS_TO_VOTE &&
+ GTFGCClientSystem()->BConnectedToMatchServer( false ) )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual void OnCommand( const char *pszCommand )
+ {
+ if ( Q_strnicmp( pszCommand, "choice", 6 ) == 0 &&
+ GetPlayerVoteState() == CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED )
+ {
+ int nIndex = atoi( pszCommand + 6 );
+ Assert( nIndex >= 0 && nIndex <= 2 );
+ if ( nIndex < 0 || nIndex > 2 )
+ return;
+
+ engine->ClientCmd( CFmtStr( "next_map_vote %d", nIndex ) );
+ }
+ }
+
+ virtual void FireGameEvent( IGameEvent *pEvent )
+ {
+ if ( FStrEq( pEvent->GetName(), "player_next_map_vote_change" ) &&
+ TFGameRules()->GetCurrentNextMapVotingState() == CTFGameRules::NEXT_MAP_VOTE_STATE_WAITING_FOR_USERS_TO_VOTE )
+ {
+ ShowVoteByOtherPlayer( pEvent->GetInt( "map_index" ) );
+ InvalidateLayout();
+ surface()->PlaySound( UTIL_GetRandomSoundFromEntry( "Vote.Cast.Yes" ) );
+
+ return;
+ }
+ else if ( FStrEq( pEvent->GetName(), "vote_maps_changed" ) )
+ {
+ InvalidateLayout( false, true );
+ }
+ }
+
+ virtual void OnEnter() OVERRIDE
+ {
+ // To get the voting options setup how they're supposed to be
+ InvalidateLayout( true, false);
+
+ CTFMatchmakingPopup::OnEnter();
+ }
+
+private:
+
+ void SetMapChoiceSettings()
+ {
+ for ( int nIndex = 0; nIndex < NEXT_MAP_VOTE_OPTIONS; ++nIndex )
+ {
+ const MapDef_t* pMapDef = NULL;
+
+ if ( TFGameRules() )
+ {
+ pMapDef = GetItemSchema()->GetMasterMapDefByIndex( TFGameRules()->GetNextMapVoteOption( nIndex ) );
+ }
+ else
+ {
+ pMapDef = GetItemSchema()->GetMasterMapDefByIndex( RandomInt( 1, GetItemSchema()->GetMapCount() - 1 ) );
+ }
+
+ Assert( pMapDef );
+ if ( !pMapDef )
+ return;
+
+ EditablePanel* pMapChoice = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", nIndex ), true );
+ if ( pMapChoice )
+ {
+ ScalableImagePanel* pMapImage = pMapChoice->FindControl< ScalableImagePanel >( "MapImage", true );
+
+ // The image
+ if ( pMapImage )
+ {
+ m_arMapPanels[ nIndex ].pMapImage = pMapImage;
+ char imagename[ 512 ];
+ Q_snprintf( imagename, sizeof( imagename ), "..\\vgui\\maps\\menu_thumb_%s", pMapDef->pszMapName );
+ pMapImage->SetImage( imagename );
+ }
+
+ // Label text
+ pMapChoice->SetDialogVariable( "mapname", g_pVGuiLocalize->Find( pMapDef->pszMapNameLocKey ) );
+ m_arMapPanels[ nIndex ].pMapNameLabel = pMapChoice->FindControl< Label >( "NameLabel" );
+
+ // Fixup the button
+ Button* pButton = pMapChoice->FindControl< Button >( "SelectButton" );
+ if ( pButton )
+ {
+ m_arMapPanels[ nIndex ].pChooseButton = pButton;
+ pButton->SetCommand( CFmtStr( "choice%d", nIndex ) );
+ // Dont let people click anymore if the've already voted
+ pButton->SetEnabled( GetPlayerVoteState() == CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED );
+ pButton->SetMouseInputEnabled( GetPlayerVoteState() == CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED );
+
+ // Give the one the user selected a green border
+ if ( GetPlayerVoteState() == nIndex )
+ {
+ pButton->SetArmed( true );
+ pButton->MakeReadyForUse();
+ pButton->SetArmedColor( pButton->GetButtonArmedFgColor(), scheme()->GetIScheme( GetScheme() )->GetColor( "CreditsGreen", Color( 94, 150, 49, 255 ) ) );
+ }
+ }
+ }
+ }
+ }
+
+ void ShowVoteByOtherPlayer( int nIndex )
+ {
+ EditablePanel* pMapChoice = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", nIndex ), true );
+ if ( pMapChoice )
+ {
+ // Play animation on the map that got voted on
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapChoice, "MapVoted" );
+ }
+ }
+
+ void UpdateVoteCounts()
+ {
+ int nVotes[ CTFGameRules::EUserNextMapVote::NUM_VOTE_STATES ];
+ memset( nVotes, 0, sizeof( nVotes ) );
+ int nTotalVotes = 0;
+
+ CTFGameRules::EUserNextMapVote eWinningVote = CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED;
+ if ( TFGameRules() )
+ {
+ TFGameRules()->GetWinningVote( nVotes );
+ }
+ else
+ {
+ // For testing on the main menu
+ for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i )
+ {
+ nVotes[ i ] += RandomInt( 0, 10 );
+ eWinningVote = (CTFGameRules::EUserNextMapVote)( nVotes[ i ] >= nVotes[ eWinningVote ] ? i : eWinningVote );
+ }
+ }
+
+ // Calculate the total so we can do a % breakdown
+ for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i )
+ {
+ nTotalVotes += nVotes[ i ];
+ }
+
+ for ( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i )
+ {
+ float flPercent = nTotalVotes ? (float)nVotes[ i ] / nTotalVotes * 100.f : 0.f;
+ EditablePanel* pMapChoicePanel = FindControl< EditablePanel >( CFmtStr( "MapChoice%d", i ), true );
+ if ( pMapChoicePanel )
+ {
+ // Update the label with the % total
+ pMapChoicePanel->SetDialogVariable( "votes", CFmtStr( "%3.0f%%", flPercent ) );
+ // Do a color change animation
+ if ( g_pClientMode && g_pClientMode->GetViewport() )
+ {
+ g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( pMapChoicePanel, i == eWinningVote ? "LosingNextMapVote" : "WinningNextMapVote" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pMapChoicePanel, i == eWinningVote ? "WinningNextMapVote" : "LosingNextMapVote" );
+ }
+ }
+ }
+ }
+
+ CTFGameRules::EUserNextMapVote GetPlayerVoteState()
+ {
+ if ( TFGameRules() )
+ {
+ int nPlayerIndex = GetLocalPlayerIndex();
+ return TFGameRules()->PlayerNextMapVoteState( nPlayerIndex );
+ }
+
+ return CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED;
+ }
+
+ CircularProgressBar* m_pTimerProgressBar;
+
+ struct MapChoice_t
+ {
+ ScalableImagePanel* pMapImage;
+ Label* pMapNameLabel;
+ Button* pChooseButton;
+ };
+ MapChoice_t m_arMapPanels[3];
+};
+
+REG_MM_POPUP_FACTORY( CNextMapVotingDashboardState, "NextMapVoting", "resource/UI/MatchMakingDashboardPopup_NextMapVoting.res" ) \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_winner.cpp b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_winner.cpp
new file mode 100644
index 0000000..1658fb0
--- /dev/null
+++ b/game/client/tf/vgui/tf_matchmaking_dashboard_next_map_winner.cpp
@@ -0,0 +1,94 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "tf_matchmaking_dashboard.h"
+#include "tf_gamerules.h"
+#include "tf_gc_client.h"
+#include <vgui/ISurface.h>
+
+using namespace vgui;
+using namespace GCSDK;
+
+#ifdef STAGING_ONLY
+extern ConVar tf_mm_popup_state_override;
+#endif
+
+class CNextMapWinnerDashboardState : public CTFMatchmakingPopup
+{
+public:
+
+ CNextMapWinnerDashboardState( const char* pszName, const char* pszResFile )
+ : CTFMatchmakingPopup( pszName, pszResFile )
+ {
+ }
+
+ virtual bool ShouldBeActve() const OVERRIDE
+ {
+#ifdef STAGING_ONLY
+ if ( FStrEq( const_cast<CNextMapWinnerDashboardState*>(this)->GetName(), tf_mm_popup_state_override.GetString() ) )
+ return true;
+#endif
+
+ int nVotes[ CTFGameRules::EUserNextMapVote::NUM_VOTE_STATES ];
+ CTFGameRules::EUserNextMapVote eWinningVote = TFGameRules()->GetWinningVote( nVotes );
+
+ if ( BInEndOfMatch() &&
+ TFGameRules() &&
+ TFGameRules()->GetCurrentNextMapVotingState() == CTFGameRules::NEXT_MAP_VOTE_STATE_MAP_CHOSEN_PAUSE &&
+ eWinningVote != CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED &&
+ GTFGCClientSystem()->BConnectedToMatchServer( false ) )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+
+ virtual void OnEnter() OVERRIDE
+ {
+ CTFMatchmakingPopup::OnEnter();
+
+ MapDefIndex_t nWinningDefIndex;
+ if ( TFGameRules() )
+ {
+ int nVotes[ CTFGameRules::EUserNextMapVote::NUM_VOTE_STATES ];
+ CTFGameRules::EUserNextMapVote eWinningVote = TFGameRules()->GetWinningVote( nVotes );
+ nWinningDefIndex = TFGameRules()->GetNextMapVoteOption( eWinningVote );
+ }
+ else
+ {
+ Assert( false );
+ nWinningDefIndex = RandomInt( 1, GetItemSchema()->GetMapCount() - 1 );
+ }
+
+ // Sound effect for success
+ surface()->PlaySound( UTIL_GetRandomSoundFromEntry( "Vote.Passed" ) );
+
+ const MapDef_t *pMapDef = GetItemSchema()->GetMasterMapDefByIndex( nWinningDefIndex );
+ if ( pMapDef )
+ {
+ Label* pMapNameLabel = FindControl< Label >( "NameLabel", true );
+ if ( pMapNameLabel )
+ {
+ pMapNameLabel->SetText( g_pVGuiLocalize->Find( pMapDef->pszMapNameLocKey ) );
+ }
+
+ ScalableImagePanel* pMapImage = FindControl< ScalableImagePanel >( "MapImage", true );
+ if ( pMapImage )
+ {
+ char imagename[ 512 ];
+ Q_snprintf( imagename, sizeof( imagename ), "..\\vgui\\maps\\menu_thumb_%s", pMapDef->pszMapName );
+ pMapImage->SetImage( imagename );
+ }
+ }
+ }
+};
+
+REG_MM_POPUP_FACTORY( CNextMapWinnerDashboardState, "NextMapWinner", "resource/UI/MatchMakingDashboardPopup_NextMapWinner.res" ) \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_matchmaking_panel.cpp b/game/client/tf/vgui/tf_matchmaking_panel.cpp
new file mode 100644
index 0000000..fae794a
--- /dev/null
+++ b/game/client/tf/vgui/tf_matchmaking_panel.cpp
@@ -0,0 +1,580 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_matchmaking_panel.h"
+#include "ienginevgui.h"
+#include "c_tf_gamestats.h"
+#include "clientmode_tf.h"
+#include "tf_hud_mainmenuoverride.h"
+#include "vgui_int.h"
+#include "IGameUIFuncs.h" // for key bindings
+#include <vgui_controls/AnimationController.h>
+#include "vgui/IInput.h"
+#include "tf_gc_client.h"
+#include "tf_party.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
+extern void ShowEconRequirementDialog( const char *pTitle, const char *pText, const char *pItemDefName );
+
+CMatchMakingPanel *GetMatchMakingPanel()
+{
+ CMatchMakingPanel *pMatchMakingPanel = (CMatchMakingPanel*)gViewPortInterface->FindPanelByName( PANEL_MATCHMAKING );
+ return pMatchMakingPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CMatchMakingPanel::CMatchMakingPanel( IViewPort *pViewPort ) : EditablePanel( NULL, PANEL_MATCHMAKING ), m_iMMPanelKey( BUTTON_CODE_INVALID )
+{
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+ ListenForGameEvent( "client_beginconnect" );
+
+ EditablePanel *m_pMainContainer = new EditablePanel( this, "MainContainer" );
+ Assert( m_pMainContainer );
+
+ m_pCompetitiveModeGroupPanel = new vgui::EditablePanel( m_pMainContainer, "CompetitiveModeGroupBox" );
+ m_pModeLabel = new vgui::Label( m_pCompetitiveModeGroupPanel, "LadderLabel", "" );
+ m_pModeComboBox = new vgui::ComboBox( m_pCompetitiveModeGroupPanel, "ModeComboBox", 3, false );
+ m_pModeComboBox->AddActionSignalTarget( this );
+ m_pStopSearchButton = new vgui::Button( m_pCompetitiveModeGroupPanel, "StopSearchButton", "" );
+ m_pStopSearchButton->AddActionSignalTarget( this );
+ m_pSearchButton = new vgui::Button( m_pCompetitiveModeGroupPanel, "SearchButton", "" );
+ m_pSearchButton->AddActionSignalTarget( this );
+ m_pSearchActiveGroupBox = new vgui::EditablePanel( m_pMainContainer, "SearchActiveGroupBox" );
+ m_pSearchActiveTitleLabel = new vgui::Label( m_pSearchActiveGroupBox, "SearchActiveTitle", "" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CMatchMakingPanel::~CMatchMakingPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::AttachToGameUI( void )
+{
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "tf_matchmaking_panel" );
+
+ if ( GetClientModeTFNormal()->GameUI() )
+ {
+ GetClientModeTFNormal()->GameUI()->SetMainMenuOverride( GetVPanel() );
+ }
+
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+ SetCursor(dc_arrow);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CMatchMakingPanel::GetName( void )
+{
+ return PANEL_MATCHMAKING;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/MatchMakingPanel.res" );
+
+ // The outer dim / close button
+ {
+ Button *pButton = FindControl< Button >( "OutsideCloseButton" );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( this );
+ pButton->SetPaintBackgroundEnabled( false );
+ }
+ }
+
+ if ( m_pModeComboBox && m_pModeComboBox->IsVisible() )
+ {
+ // Placeholder
+ m_pModeComboBox->RemoveAll();
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pModeComboBox->SetFont( hFont );
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "bracket", k_nMatchGroup_Ladder_6v6 );
+ m_pModeComboBox->AddItem( "6v6", pKeyValues );
+ pKeyValues->SetInt( "bracket", k_nMatchGroup_Ladder_9v9 );
+ m_pModeComboBox->AddItem( "9v9", pKeyValues );
+ pKeyValues->deleteThis();
+ m_pModeComboBox->ActivateItemByRow( 0 );
+ }
+
+ if ( m_pSearchButton )
+ {
+ m_pSearchButton->AddActionSignalTarget( this );
+ }
+ if ( m_pStopSearchButton )
+ {
+ m_pStopSearchButton->AddActionSignalTarget( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::OnCommand( const char *pCommand )
+{
+ if ( FStrEq( pCommand, "close" ) )
+ {
+ if ( enginevgui->IsGameUIVisible() )
+ {
+ ShowPanel( false );
+ }
+ else
+ {
+ IViewPortPanel *pMMPanel = ( gViewPortInterface->FindPanelByName( PANEL_MATCHMAKING ) );
+ if ( pMMPanel )
+ {
+ gViewPortInterface->ShowPanel( pMMPanel, false );
+ }
+ }
+ }
+ else if ( FStrEq( pCommand, "search" ) )
+ {
+ StartSearch();
+ }
+ else if ( FStrEq( pCommand, "stopsearch" ) )
+ {
+ StopSearch();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::OnTextChanged( KeyValues *data )
+{
+ if ( !IsVisible() )
+ return;
+
+ if ( !GCClientSystem()->BConnectedtoGC() )
+ return;
+
+ Panel *pPanel = reinterpret_cast< vgui::Panel* >( data->GetPtr( "panel" ) );
+
+ vgui::ComboBox *pComboBox = dynamic_cast< vgui::ComboBox* >( pPanel );
+ if ( pComboBox )
+ {
+ if ( pComboBox == m_pModeComboBox )
+ {
+ KeyValues *pUserData = m_pModeComboBox->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ uint32 unModeType = pUserData->GetInt( "bracket", (uint32)-1 );
+
+ if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last )
+ {
+ GTFGCClientSystem()->SetLadderType( unModeType );
+ }
+// else if ( unModeType >= k_nMatchGroup_Quickplay_First && unModeType <= k_nMatchGroup_Quickplay_Last )
+// {
+// GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_QUICKPLAY );
+// }
+
+ return;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "gameui_hidden" ) )
+ {
+ ShowPanel( false );
+ return;
+ }
+ else if ( FStrEq( event->GetName(), "client_beginconnect" ) )
+ {
+ ShowPanel( false );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::ShowPanel( bool bShow )
+{
+ // Snag this so we know what to listen for
+ m_iMMPanelKey = gameuifuncs->GetButtonCodeForBind( "show_matchmaking" );
+
+ if ( bShow
+ && ( !steamapicontext
+ || !steamapicontext->SteamUtils()
+ || !steamapicontext->SteamMatchmakingServers()
+ || !steamapicontext->SteamUser()
+ || !steamapicontext->SteamUser()->BLoggedOn() ) )
+ {
+ Warning( "Steam not properly initialized or connected.\n" );
+ ShowMessageBox( "#TF_MM_GenericFailure_Title", "#TF_MM_GenericFailure", "#GameUI_OK" );
+ ShowPanel( false );
+ }
+
+ if ( bShow && !GCClientSystem()->BConnectedtoGC() )
+ {
+ Warning( "Not connected to GC.\n" );
+ ShowMessageBox( "#TF_MM_NoGC_Title", "#TF_MM_NoGC", "#GameUI_OK" );
+ ShowPanel( false );
+ }
+
+ bool bInMMGame = GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_InGame &&
+ GTFGCClientSystem()->GetAssignedMatchAbandonStatus() == k_EAbandonGameStatus_AbandonWithPenalty;
+ bool bSearching = GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_InQueue;
+
+ // DevMsg( "CMatchmakingPanel shown, bInMMGame %d bSearching %d\n", bInMMGame, bSearching );
+
+ if ( m_pModeLabel )
+ {
+ m_pModeLabel->SetVisible( true );
+ }
+ if ( m_pModeComboBox )
+ {
+ m_pModeComboBox->SetEnabled( !bSearching && !bInMMGame );
+ m_pModeComboBox->SetVisible( !bSearching );
+ }
+ if ( m_pSearchButton )
+ {
+ m_pSearchButton->SetEnabled( !bSearching && !bInMMGame );
+ m_pSearchButton->SetVisible( !bSearching && !bInMMGame );
+ }
+ if ( m_pStopSearchButton )
+ {
+ m_pStopSearchButton->SetEnabled( bSearching && !bInMMGame );
+ m_pStopSearchButton->SetVisible( bSearching && !bInMMGame );
+ }
+ if ( m_pSearchActiveGroupBox )
+ {
+ m_pSearchActiveGroupBox->SetVisible( bSearching && !bInMMGame );
+ }
+
+ if ( bShow )
+ {
+ KeyValues *pUserData = m_pModeComboBox->GetActiveItemUserData();
+ if ( pUserData )
+ {
+ uint32 unModeType = pUserData->GetInt( "bracket", (uint32)-1 );
+
+ if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last )
+ {
+ GTFGCClientSystem()->SetLadderType( unModeType );
+ }
+ }
+ }
+
+ SetVisible( bShow );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::OnKeyCodePressed( KeyCode code )
+{
+ if ( code == m_iMMPanelKey )
+ {
+ ShowPanel( false );
+ return;
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::OnKeyCodeTyped( KeyCode code )
+{
+ if ( code == KEY_ESCAPE )
+ {
+ if ( IsVisible() )
+ {
+ SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::OnThink()
+{
+ if ( GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_SEARCHING )
+ {
+ const CMsgMatchmakingProgress &progress = GTFGCClientSystem()->m_msgMatchmakingProgress;
+ wchar_t wszCount[32];
+ CUtlVector< vgui::Label* > vecNearbyFields;
+ vgui::Label *pNearbyColumnHead = dynamic_cast< vgui::Label* >( FindChildByName( "NearbyColumnHead", true ) );
+ Assert( pNearbyColumnHead );
+ vecNearbyFields.AddToTail( pNearbyColumnHead );
+
+#define DO_FIELD( protobufname, labelname, bNearby ) \
+ vgui::Label *p##labelname = dynamic_cast< vgui::Label* >( FindChildByName( #labelname, true ) ); \
+ Assert( p##labelname ); \
+ if ( p##labelname ) \
+ { \
+ if ( progress.has_##protobufname() ) \
+ { \
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", progress.protobufname() ); \
+ p##labelname->SetText( wszCount ); \
+ if ( bNearby ) bHasAnyNearbyData = true; \
+ } \
+ else \
+ { \
+ p##labelname->SetText( "#TF_Matchmaking_NoData" ); \
+ } \
+ if ( bNearby ) vecNearbyFields.AddToTail( p##labelname ); \
+ }
+
+ bool bHasAnyNearbyData = false;
+ DO_FIELD( matching_worldwide_searching_players, PlayersSearchingMatchingWorldwideValue, false )
+ DO_FIELD( matching_near_you_searching_players, PlayersSearchingMatchingNearbyValue, true )
+ DO_FIELD( matching_worldwide_active_players, PlayersInGameMatchingWorldwideValue, false )
+ DO_FIELD( matching_near_you_active_players, PlayersInGameMatchingNearbyValue, true )
+ DO_FIELD( matching_worldwide_empty_gameservers, EmptyGameserversMatchingWorldwideValue, false )
+ DO_FIELD( matching_near_you_empty_gameservers, EmptyGameserversMatchingNearbyValue, true )
+ DO_FIELD( total_worldwide_searching_players, PlayersSearchingTotalWorldwideValue, false )
+ DO_FIELD( total_near_you_searching_players, PlayersSearchingTotalNearbyValue, true )
+ DO_FIELD( total_worldwide_active_players, PlayersInGameTotalWorldwideValue, false )
+ DO_FIELD( total_near_you_active_players, PlayersInGameTotalNearbyValue, true )
+
+ FOR_EACH_VEC( vecNearbyFields, i )
+ {
+ vecNearbyFields[i]->SetVisible( bHasAnyNearbyData );
+ }
+
+ // HOLY CHEESEBALL BUSY INDICATOR
+ const wchar_t *pwszEllipses = &L"....."[4 - ( (unsigned)Plat_FloatTime() % 5U )];
+ wchar_t wszLocalized[512];
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Searching" ), 1, pwszEllipses );
+ m_pSearchActiveTitleLabel->SetText( wszLocalized );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::SetVisible( bool bState )
+{
+ if ( bState == true )
+ {
+// IGameEvent *event = gameeventmanager->CreateEvent( "questlog_opened" );
+// if ( event )
+// {
+// gameeventmanager->FireEventClientSide( event );
+// }
+
+ if ( enginevgui->IsGameUIVisible() )
+ {
+ AttachToGameUI();
+ }
+ else
+ {
+ ipanel()->SetParent( GetVPanel(), VGui_GetClientDLLRootPanel() );
+
+ MakePopup( false, true );
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+ MoveToFront();
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" );
+ vgui::surface()->PlaySound( "ui/panel_open.wav" );
+ }
+ else if ( IsVisible() )
+ {
+ // Detach from the GameUI when we hide
+ IViewPortPanel *pMMOverride = gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE );
+ if ( pMMOverride )
+ {
+ ((CHudMainMenuOverride*)pMMOverride)->AttachToGameUI();
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" );
+ vgui::surface()->PlaySound( "ui/panel_close.wav" );
+ }
+
+ BaseClass::SetVisible( bState );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::StartSearch( void )
+{
+#ifdef STAGING_ONLY
+ GTFGCClientSystem()->EndMatchmaking();
+
+ bool bCompetitive = GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_LADDER;
+
+ // Solo
+ CTFParty *pParty = GTFGCClientSystem()->GetParty();
+ if ( !pParty || pParty->GetNumMembers() <= 1 )
+ {
+ if ( bCompetitive && !GTFGCClientSystem()->BHasCompetitiveAccess() )
+ {
+ ShowEconRequirementDialog( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", CTFItemSchema::k_rchLadderPassItemDefName );
+ return;
+ }
+ else if ( bCompetitive && !CheckCompetitiveConvars() )
+ {
+ ShowMessageBox( "#TF_Competitive_Convars_CantProceed_Title", "#TF_Competitive_Convars_CantProceed", "#GameUI_OK" );
+ return;
+ }
+ }
+ // Group
+ else
+ {
+ wchar_t wszLocalized[512];
+ char szLocalized[512];
+ wchar_t wszCharPlayerName[128];
+
+ bool bAnyMembersWithoutAuth = false;
+
+ for ( int i = 0; i < pParty->GetNumMembers(); ++i )
+ {
+ if ( bCompetitive )
+ {
+ if ( !pParty->Obj().members( i ).competitive_access() )
+ {
+ bAnyMembersWithoutAuth = true;
+ V_UTF8ToUnicode( steamapicontext->SteamFriends()->GetFriendPersonaName( pParty->GetMember( i ) ), wszCharPlayerName, sizeof( wszCharPlayerName ) );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_MissingPass" ), 1, wszCharPlayerName );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) );
+
+ GTFGCClientSystem()->SendSteamLobbyChat( CTFGCClientSystem::k_eLobbyMsg_SystemMsgFromLeader, szLocalized );
+ }
+ }
+ }
+
+ if ( bAnyMembersWithoutAuth )
+ {
+ ShowMessageBox( "#TF_Competitive_RequiresPass_Title", "#TF_Competitive_RequiresPass", "#GameUI_OK" );
+ return;
+ }
+ }
+
+ {
+ KeyValues *pUserData = m_pModeComboBox->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ uint32 unModeType = pUserData->GetInt( "bracket", (uint32)-1 );
+ if ( unModeType >= k_nMatchGroup_Ladder_First && unModeType <= k_nMatchGroup_Ladder_Last )
+ {
+ GTFGCClientSystem()->BeginMatchmaking( TF_Matchmaking_LADDER );
+ }
+ else if ( unModeType >= k_nMatchGroup_Quickplay_First && unModeType <= k_nMatchGroup_Quickplay_Last )
+ {
+ // No longer support quickplay
+ Assert( false );
+ }
+ }
+
+
+ GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING );
+
+ if ( m_pModeLabel )
+ {
+ m_pModeLabel->SetVisible( false );
+ }
+ if ( m_pModeComboBox )
+ {
+ m_pModeComboBox->SetEnabled( false );
+ m_pModeComboBox->SetVisible( false );
+ }
+ if ( m_pSearchButton )
+ {
+ m_pSearchButton->SetEnabled( false );
+ m_pSearchButton->SetVisible( false );
+ }
+ if ( m_pStopSearchButton )
+ {
+ m_pStopSearchButton->SetEnabled( true );
+ m_pStopSearchButton->SetVisible( true );
+ }
+ if ( m_pSearchActiveGroupBox )
+ {
+ m_pSearchActiveGroupBox->SetVisible( true );
+ }
+#endif // STAGING_ONLY
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMatchMakingPanel::StopSearch( void )
+{
+ if ( m_pModeLabel )
+ {
+ m_pModeLabel->SetVisible( true );
+ }
+ if ( m_pModeComboBox )
+ {
+ m_pModeComboBox->SetEnabled( true );
+ m_pModeComboBox->SetVisible( true );
+ }
+ if ( m_pSearchButton )
+ {
+ m_pSearchButton->SetEnabled( true );
+ m_pSearchButton->SetVisible( true );
+ }
+ if ( m_pStopSearchButton )
+ {
+ m_pStopSearchButton->SetEnabled( false );
+ m_pStopSearchButton->SetVisible( false );
+ }
+ if ( m_pSearchActiveGroupBox )
+ {
+ m_pSearchActiveGroupBox->SetVisible( false );
+ }
+
+ GTFGCClientSystem()->EndMatchmaking();
+}
+
+#ifdef STAGING_ONLY
+// Just VGUI things...
+static void cc_tf_mm_panel_reload()
+{
+ CMatchMakingPanel *pPanel = GetMatchMakingPanel();
+ if ( pPanel )
+ {
+ pPanel->InvalidateLayout( true, true );
+ gViewPortInterface->ShowPanel( pPanel, true );
+ }
+}
+ConCommand tf_mm_panel_reload( "tf_mm_panel_reload", cc_tf_mm_panel_reload );
+#endif
diff --git a/game/client/tf/vgui/tf_matchmaking_panel.h b/game/client/tf/vgui/tf_matchmaking_panel.h
new file mode 100644
index 0000000..fa7ae40
--- /dev/null
+++ b/game/client/tf/vgui/tf_matchmaking_panel.h
@@ -0,0 +1,75 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef MATCH_MAKING_PANEL_H
+#define MATCH_MAKING_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include <game/client/iviewport.h>
+#include "quest_log_panel.h"
+
+using namespace vgui;
+
+class CBaseLobbyPanel;
+class CMatchMakingPanel *GetMatchMakingPanel();
+
+//-----------------------------------------------------------------------------
+// The default quest log panel
+//-----------------------------------------------------------------------------
+class CMatchMakingPanel : public EditablePanel, public IViewPortPanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CMatchMakingPanel, EditablePanel );
+public:
+ CMatchMakingPanel( IViewPort *pViewPort );
+ virtual ~CMatchMakingPanel();
+
+ void AttachToGameUI();
+ virtual const char *GetName( void ) OVERRIDE;
+ virtual void SetData( KeyValues *data ) OVERRIDE {}
+ virtual void Reset() OVERRIDE { Update(); SetVisible( true ); }
+ virtual void Update() OVERRIDE { return; }
+ virtual bool NeedsUpdate( void ) OVERRIDE { return false; }
+ virtual bool HasInputElements( void ) OVERRIDE { return true; }
+ virtual void ShowPanel( bool bShow ) OVERRIDE;
+
+ // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui
+ vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); }
+ virtual bool IsVisible() OVERRIDE { return BaseClass::IsVisible(); }
+ virtual void SetParent( vgui::VPANEL parent ) OVERRIDE { BaseClass::SetParent( parent ); }
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_MENUCONTROLS; }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnCommand( const char *pCommand ) OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+ virtual void SetVisible( bool bState ) OVERRIDE;
+ virtual void OnKeyCodePressed( KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodeTyped(KeyCode code) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+
+private:
+ void StartSearch( void );
+ void StopSearch( void );
+
+ ButtonCode_t m_iMMPanelKey;
+ vgui::EditablePanel *m_pMainContainer;
+ vgui::EditablePanel *m_pCompetitiveModeGroupPanel;
+ vgui::Label *m_pModeLabel;
+ vgui::ComboBox *m_pModeComboBox;
+ vgui::Button *m_pStopSearchButton;
+ vgui::Button *m_pSearchButton;
+ vgui::EditablePanel *m_pSearchActiveGroupBox;
+ vgui::Label *m_pSearchActiveTitleLabel;
+};
+
+#endif // MATCH_MAKING_PANEL_H
diff --git a/game/client/tf/vgui/tf_mouseforwardingpanel.cpp b/game/client/tf/vgui/tf_mouseforwardingpanel.cpp
new file mode 100644
index 0000000..e9a47fa
--- /dev/null
+++ b/game/client/tf/vgui/tf_mouseforwardingpanel.cpp
@@ -0,0 +1,81 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "tf_mouseforwardingpanel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//=============================================================================//
+
+DECLARE_BUILD_FACTORY( CMouseMessageForwardingPanel );
+
+CMouseMessageForwardingPanel::CMouseMessageForwardingPanel( Panel *parent, const char *name ) : BaseClass( parent, name )
+{
+ // don't draw an
+ SetPaintEnabled(false);
+ SetPaintBackgroundEnabled(false);
+ SetPaintBorderEnabled(false);
+}
+
+void CMouseMessageForwardingPanel::PerformLayout()
+{
+ // fill out the whole area
+ int w, t;
+ GetParent()->GetSize(w, t);
+ SetBounds(0, 0, w, t);
+}
+
+void CMouseMessageForwardingPanel::OnCursorEntered()
+{
+ if ( GetParent() )
+ {
+ GetParent()->OnCursorEntered();
+ }
+}
+
+void CMouseMessageForwardingPanel::OnCursorExited()
+{
+ if ( GetParent() )
+ {
+ GetParent()->OnCursorExited();
+ }
+}
+
+void CMouseMessageForwardingPanel::OnMousePressed( vgui::MouseCode code )
+{
+ if ( GetParent() )
+ {
+ GetParent()->OnMousePressed( code );
+ }
+}
+
+void CMouseMessageForwardingPanel::OnMouseReleased( vgui::MouseCode code )
+{
+ if ( GetParent() )
+ {
+ GetParent()->OnMouseReleased( code );
+ }
+}
+
+void CMouseMessageForwardingPanel::OnMouseDoublePressed( vgui::MouseCode code )
+{
+ if ( GetParent() )
+ {
+ GetParent()->OnMouseDoublePressed( code );
+ }
+}
+
+void CMouseMessageForwardingPanel::OnMouseWheeled(int delta)
+{
+ if ( GetParent() )
+ {
+ GetParent()->OnMouseWheeled( delta );
+ }
+}
+
diff --git a/game/client/tf/vgui/tf_mouseforwardingpanel.h b/game/client/tf/vgui/tf_mouseforwardingpanel.h
new file mode 100644
index 0000000..e260a2e
--- /dev/null
+++ b/game/client/tf/vgui/tf_mouseforwardingpanel.h
@@ -0,0 +1,34 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#ifndef TF_MOUSEFORWARDINGPANEL_H
+#define TF_MOUSEFORWARDINGPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/EditablePanel.h>
+
+//-----------------------------------------------------------------------------
+// Purpose: Invisible panel that forwards up mouse movement
+//-----------------------------------------------------------------------------
+class CMouseMessageForwardingPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CMouseMessageForwardingPanel, vgui::EditablePanel );
+public:
+ CMouseMessageForwardingPanel( Panel *parent, const char *name );
+
+ virtual void PerformLayout( void );
+ virtual void OnCursorEntered();
+ virtual void OnCursorExited();
+ virtual void OnMousePressed( vgui::MouseCode code );
+ virtual void OnMouseReleased( vgui::MouseCode code );
+ virtual void OnMouseDoublePressed( vgui::MouseCode code );
+ virtual void OnMouseWheeled(int delta);
+};
+
+#endif // TF_MOUSEFORWARDINGPANEL_H
diff --git a/game/client/tf/vgui/tf_overview.cpp b/game/client/tf/vgui/tf_overview.cpp
new file mode 100644
index 0000000..96b626b
--- /dev/null
+++ b/game/client/tf/vgui/tf_overview.cpp
@@ -0,0 +1,859 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include <vgui/ISurface.h>
+#include <vgui/ILocalize.h>
+#include "tf_shareddefs.h"
+#include "tf_overview.h"
+#include "c_playerresource.h"
+#include "c_tf_objective_resource.h"
+#include "usermessages.h"
+#include "coordsize.h"
+#include "clientmode.h"
+#include <vgui_controls/AnimationController.h>
+#include "voice_status.h"
+#include "spectatorgui.h"
+#include "c_team_objectiveresource.h"
+
+using namespace vgui;
+
+void __MsgFunc_UpdateRadar( bf_read &msg )
+{
+ if ( !g_pMapOverview )
+ return;
+
+ int iPlayerEntity = msg.ReadByte();
+
+ while ( iPlayerEntity > 0 )
+ {
+ int x = msg.ReadSBitLong( COORD_INTEGER_BITS-1 ) * 4;
+ int y = msg.ReadSBitLong( COORD_INTEGER_BITS-1 ) * 4;
+ int a = msg.ReadSBitLong( 9 );
+
+ Vector origin( x, y, 0 );
+ QAngle angles( 0, a, 0 );
+
+ g_pMapOverview->SetPlayerPositions( iPlayerEntity-1, origin, angles );
+
+ iPlayerEntity = msg.ReadByte(); // read index for next player
+ }
+}
+
+extern ConVar _overview_mode;
+ConVar _cl_minimapzoom( "_cl_minimapzoom", "1", FCVAR_ARCHIVE );
+ConVar _overview_mode( "_overview_mode", "1", FCVAR_ARCHIVE, "Overview mode - 0=off, 1=inset, 2=full\n", true, 0, true, 2 );
+
+
+CTFMapOverview *GetTFOverview( void )
+{
+ return dynamic_cast<CTFMapOverview *>( g_pMapOverview );
+}
+
+// overview_togglezoom rotates through 3 levels of zoom for the small map
+//-----------------------------------------------------------------------
+void ToggleZoom( void )
+{
+ if ( !GetTFOverview() )
+ return;
+
+ GetTFOverview()->ToggleZoom();
+}
+static ConCommand overview_togglezoom( "overview_togglezoom", ToggleZoom );
+
+// overview_largemap toggles showing the large map
+//------------------------------------------------
+void ShowLargeMap( void )
+{
+ if ( !GetTFOverview() )
+ return;
+
+ GetTFOverview()->ShowLargeMap();
+}
+static ConCommand overview_showlargemap( "+overview_largemap", ShowLargeMap );
+
+void HideLargeMap( void )
+{
+ if ( !GetTFOverview() )
+ return;
+
+ GetTFOverview()->HideLargeMap();
+}
+static ConCommand overview_hidelargemap( "-overview_largemap", HideLargeMap );
+
+//--------------------------------
+// map border ?
+// icon minimum zoom
+// flag swipes
+// chatting icon
+// voice com icon
+//---------------------------------
+
+DECLARE_HUDELEMENT( CTFMapOverview );
+
+ConVar tf_overview_voice_icon_size( "tf_overview_voice_icon_size", "64", FCVAR_ARCHIVE );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMapOverview::CTFMapOverview( const char *pElementName ) : BaseClass( pElementName )
+{
+ InitTeamColorsAndIcons();
+ m_flIconSize = 96.0f;
+ m_iLastMode = MAP_MODE_OFF;
+ m_bDisabled = false;
+ m_nMapTextureOverlayID = -1;
+ usermessages->HookMessage( "UpdateRadar", __MsgFunc_UpdateRadar );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::Update()
+{
+ UpdateCapturePoints();
+
+ BaseClass::Update();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::VidInit( void )
+{
+ BaseClass::VidInit();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::UpdateCapturePoints()
+{
+ if ( !g_pObjectiveResource )
+ return;
+
+ Color colorGreen( 0,255,0,255 );
+
+ for( int i = 0 ; i < g_pObjectiveResource->GetNumControlPoints() ; i++ )
+ {
+ // check if CP is visible at all
+ if( !g_pObjectiveResource->IsCPVisible( i ) )
+ {
+ if ( m_CapturePoints[i] != 0 )
+ {
+ // remove capture point from map
+ RemoveObject( m_CapturePoints[i] );
+ m_CapturePoints[i] = 0;
+ }
+
+ continue;
+ }
+
+ // ok, show CP
+ int iOwningTeam = g_pObjectiveResource->GetOwningTeam(i);
+ int iCappingTeam = g_pObjectiveResource->GetCappingTeam(i);
+
+ int iOwningIcon = g_pObjectiveResource->GetIconForTeam( i, iOwningTeam );
+ if ( iOwningIcon <= 0 )
+ continue; // baah
+
+ const char *textureName = GetMaterialNameFromIndex( iOwningIcon );
+
+ int objID = m_CapturePoints[i];
+
+ if ( objID == 0 )
+ {
+ // add object if not already there
+ objID = m_CapturePoints[i] = AddObject( textureName, 0, -1 );
+
+ // objective positions never change (so far)
+ SetObjectPosition( objID, g_pObjectiveResource->GetCPPosition(i), vec3_angle );
+
+ AddObjectFlags( objID, MAP_OBJECT_ALIGN_TO_MAP );
+ }
+
+ SetObjectIcon( objID, textureName, 128.0 );
+
+ // draw cap percentage
+ if( iCappingTeam != TEAM_UNASSIGNED )
+ {
+ SetObjectStatus( objID, g_pObjectiveResource->GetCPCapPercentage(i), colorGreen );
+ }
+ else
+ {
+ SetObjectStatus( objID, -1, colorGreen ); // turn it off
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::InitTeamColorsAndIcons()
+{
+ BaseClass::InitTeamColorsAndIcons();
+
+ m_TeamColors[TF_TEAM_RED] = COLOR_TF_RED;
+ m_TeamIcons[TF_TEAM_RED] = AddIconTexture( "sprites/minimap_icons/red_player" );
+ m_CameraIcons[TF_TEAM_RED] = AddIconTexture( "sprites/minimap_icons/red_camera" );
+
+ m_TeamColors[TF_TEAM_BLUE] = COLOR_TF_BLUE;
+ m_TeamIcons[TF_TEAM_BLUE] = AddIconTexture( "sprites/minimap_icons/blue_player" );
+ m_CameraIcons[TF_TEAM_BLUE] = AddIconTexture( "sprites/minimap_icons/blue_camera" );
+
+ Q_memset( m_flPlayerChatTime, 0, sizeof(m_flPlayerChatTime ) );
+ m_iVoiceIcon = AddIconTexture( "voice/icntlk_pl" );
+ m_iChatIcon = AddIconTexture( "sprites/minimap_icons/voiceIcon" );
+
+ Q_memset( m_CapturePoints, 0, sizeof(m_CapturePoints) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::DrawCamera()
+{
+ C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
+
+ if ( !localPlayer )
+ return;
+
+ int iTexture = m_CameraIcons[localPlayer->GetTeamNumber()];
+
+ if ( localPlayer->IsObserver() || iTexture <= 0 )
+ {
+ BaseClass::DrawCamera();
+ }
+ else
+ {
+ MapObject_t obj;
+ memset( &obj, 0, sizeof(MapObject_t) );
+
+ obj.icon = iTexture;
+ obj.position = localPlayer->GetAbsOrigin();
+ obj.size = m_flIconSize * 1.5;
+ obj.angle = localPlayer->EyeAngles();
+ obj.status = -1;
+
+ DrawIcon( &obj );
+
+ DrawVoiceIconForPlayer( localPlayer->entindex() - 1 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp( type, "player_death" ) == 0 )
+ {
+ MapPlayer_t *player = GetPlayerByUserID( event->GetInt( "userid" ) );
+
+ if ( player && CanPlayerBeSeen( player ) )
+ {
+ // create skull icon for 3 seconds
+ int handle = AddObject( "sprites/minimap_icons/death", 0, 3 );
+ SetObjectText( handle, player->name, player->color );
+ SetObjectPosition( handle, player->position, player->angle );
+ }
+ }
+ else if ( Q_strcmp( type, "game_newmap" ) == 0 )
+ {
+ SetMode( _overview_mode.GetInt() );
+ }
+
+ BaseClass::FireGameEvent( event );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMapOverview::CanPlayerBeSeen( MapPlayer_t *player )
+{
+ // rules that define if you can see a player on the overview or not
+ C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
+
+ if ( !localPlayer || !player )
+ return false;
+
+ // don't draw ourselves
+ if ( localPlayer->entindex() == (player->index+1) )
+ return false;
+
+ // if local player is on spectator team, he can see everyone
+ if ( localPlayer->GetTeamNumber() <= TEAM_SPECTATOR )
+ return true;
+
+ // we never track unassigned or real spectators
+ if ( player->team <= TEAM_SPECTATOR )
+ return false;
+
+ // ingame and as dead player we can only see our own teammates
+ return ( localPlayer->GetTeamNumber() == player->team );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::ShowLargeMap( void )
+{
+ if ( IsDisabled() )
+ {
+ return;
+ }
+
+ // remember old mode
+ m_iLastMode = GetMode();
+
+ // if we hit the toggle while full, set to disappear when we release
+ if ( m_iLastMode == MAP_MODE_FULL )
+ {
+ m_iLastMode = MAP_MODE_OFF;
+ }
+
+ SetMode( MAP_MODE_FULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::HideLargeMap( void )
+{
+ if ( IsDisabled() )
+ {
+ return;
+ }
+
+ SetMode( m_iLastMode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::ToggleZoom( void )
+{
+ if ( IsDisabled() )
+ {
+ return;
+ }
+
+ if ( GetMode() != MAP_MODE_INSET )
+ return;
+
+ int iZoomLevel = ( _cl_minimapzoom.GetInt() + 1 ) % TF_MAP_ZOOM_LEVELS;
+
+ _cl_minimapzoom.SetValue( iZoomLevel );
+
+ switch( _cl_minimapzoom.GetInt() )
+ {
+ case 0:
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel1" );
+ break;
+ case 1:
+ default:
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel2" );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::SetMode(int mode)
+{
+ if ( IsDisabled() )
+ {
+ return;
+ }
+
+ m_flChangeSpeed = 0; // change size instantly
+
+ if ( mode == MAP_MODE_OFF )
+ {
+ ShowPanel( false );
+
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapOff" );
+ }
+ else if ( mode == MAP_MODE_INSET )
+ {
+ switch( _cl_minimapzoom.GetInt() )
+ {
+ case 0:
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel1" );
+ break;
+ case 1:
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel2" );
+ break;
+ case 2:
+ default:
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapZoomLevel3" );
+ break;
+ }
+
+ C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
+
+ if ( pPlayer )
+ SetFollowEntity( pPlayer->entindex() );
+
+ ShowPanel( true );
+
+ if ( m_nMode == MAP_MODE_FULL )
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "MapScaleToSmall" );
+ else
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "SnapToSmall" );
+ }
+ else if ( mode == MAP_MODE_FULL )
+ {
+ SetFollowEntity( 0 );
+
+ ShowPanel( true );
+
+ if ( m_nMode == MAP_MODE_INSET )
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ZoomToLarge" );
+ else
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "SnapToLarge" );
+ }
+
+ // finally set mode
+ m_nMode = mode;
+
+ // save in a cvar for archive
+ _overview_mode.SetValue( m_nMode );
+
+ UpdateSizeAndPosition();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::UpdateSizeAndPosition()
+{
+ // move back up if the spectator menu is not visible
+ if ( !g_pSpectatorGUI || ( !g_pSpectatorGUI->IsVisible() && GetMode() == MAP_MODE_INSET ) )
+ {
+ int x,y,w,h;
+
+ GetBounds( x,y,w,h );
+
+ y = YRES(5); // hax, align to top of the screen
+
+ SetBounds( x,y,w,h );
+ }
+
+ BaseClass::UpdateSizeAndPosition();
+}
+
+ConVar cl_voicetest( "cl_voicetest", "0", FCVAR_CHEAT );
+ConVar cl_overview_chat_time( "cl_overview_chat_time", "2.0", FCVAR_ARCHIVE );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::PlayerChat( int index )
+{
+ m_flPlayerChatTime[index-1] = gpGlobals->curtime + cl_overview_chat_time.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::DrawMapPlayers()
+{
+ BaseClass::DrawMapPlayers();
+
+ C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer();
+
+ Assert( localPlayer );
+
+ int iLocalPlayer = localPlayer->entindex() - 1;
+
+ for ( int i = 0 ; i < MAX_PLAYERS ; i++ )
+ {
+ if ( i == iLocalPlayer )
+ continue;
+
+ MapPlayer_t *player = &m_Players[i];
+
+ if ( !CanPlayerBeSeen( player ) )
+ continue;
+
+ if ( player->health <= 0 ) // don't draw dead players / spectators
+ continue;
+
+ DrawVoiceIconForPlayer( i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::DrawVoiceIconForPlayer( int playerIndex )
+{
+ Assert( playerIndex >= 0 && playerIndex < MAX_PLAYERS );
+
+ MapPlayer_t *player = &m_Players[playerIndex];
+
+ // if they just sent a chat msg, or are using voice, or did a hand signal or voice command
+ // draw a chat icon
+
+ if ( cl_voicetest.GetInt() || GetClientVoiceMgr()->IsPlayerSpeaking( player->index+1 ) )
+ {
+ MapObject_t obj;
+ memset( &obj, 0, sizeof(MapObject_t) );
+
+ obj.icon = m_iVoiceIcon;
+ obj.position = player->position;
+ obj.size = tf_overview_voice_icon_size.GetFloat();
+ obj.status = -1;
+
+ DrawIcon( &obj );
+ }
+ else if ( m_flPlayerChatTime[player->index] > gpGlobals->curtime )
+ {
+ MapObject_t obj;
+ memset( &obj, 0, sizeof(MapObject_t) );
+
+ obj.icon = m_iChatIcon;
+ obj.position = player->position;
+ obj.size = tf_overview_voice_icon_size.GetFloat();
+ obj.status = -1;
+
+ DrawIcon( &obj );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMapOverview::DrawIcon( MapObject_t *obj )
+{
+ for ( int i = 0 ; i < MAX_CONTROL_POINTS ; i++ )
+ {
+ if ( obj->objectID == m_CapturePoints[i] && obj->objectID != 0 )
+ {
+ return DrawCapturePoint( i, obj );
+ }
+ }
+
+ return BaseClass::DrawIcon( obj );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::DrawQuad( Vector pos, int scale, float angle, int textureID, int alpha )
+{
+ Vector offset;
+ offset.z = 0;
+
+ offset.x = -scale; offset.y = scale;
+ VectorYawRotate( offset, angle, offset );
+ Vector2D pos1 = WorldToMap( pos + offset );
+
+ offset.x = scale; offset.y = scale;
+ VectorYawRotate( offset, angle, offset );
+ Vector2D pos2 = WorldToMap( pos + offset );
+
+ offset.x = scale; offset.y = -scale;
+ VectorYawRotate( offset, angle, offset );
+ Vector2D pos3 = WorldToMap( pos + offset );
+
+ offset.x = -scale; offset.y = -scale;
+ VectorYawRotate( offset, angle, offset );
+ Vector2D pos4 = WorldToMap( pos + offset );
+
+ Vertex_t points[4] =
+ {
+ Vertex_t( MapToPanel ( pos1 ), Vector2D(0,0) ),
+ Vertex_t( MapToPanel ( pos2 ), Vector2D(1,0) ),
+ Vertex_t( MapToPanel ( pos3 ), Vector2D(1,1) ),
+ Vertex_t( MapToPanel ( pos4 ), Vector2D(0,1) )
+ };
+
+ surface()->DrawSetColor( 255, 255, 255, alpha );
+ surface()->DrawSetTexture( textureID );
+ surface()->DrawTexturedPolygon( 4, points );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMapOverview::DrawCapturePoint( int iCP, MapObject_t *obj )
+{
+ int textureID = obj->icon;
+ Vector pos = obj->position;
+ float scale = obj->size;
+
+ Vector2D pospanel = WorldToMap( pos );
+ pospanel = MapToPanel( pospanel );
+
+ if ( !IsInPanel( pospanel ) )
+ return false; // player is not within overview panel
+
+ // draw capture swipe
+ DrawQuad( pos, scale, 0, textureID, 255 );
+
+ int iCappingTeam = g_pObjectiveResource->GetCappingTeam( iCP );
+
+ if ( iCappingTeam != TEAM_UNASSIGNED )
+ {
+ int iCapperIcon = g_pObjectiveResource->GetCPCappingIcon( iCP );
+ const char *textureName = GetMaterialNameFromIndex( iCapperIcon );
+
+ float flCapPercent = g_pObjectiveResource->GetCPCapPercentage(iCP);
+ bool bSwipeLeft = ( iCappingTeam == TF_TEAM_RED ) ? true : false;
+
+ DrawHorizontalSwipe( pos, scale, AddIconTexture( textureName ), flCapPercent, bSwipeLeft );
+ }
+
+ // fixup for noone is capping, but someone is in the area
+ int iNumBlue = g_pObjectiveResource->GetNumPlayersInArea( iCP, TF_TEAM_BLUE );
+ int iNumRed = g_pObjectiveResource->GetNumPlayersInArea( iCP, TF_TEAM_RED );
+
+ int iOwningTeam = g_pObjectiveResource->GetOwningTeam( iCP );
+ if ( iCappingTeam == TEAM_UNASSIGNED )
+ {
+ if ( iNumBlue > 0 && iNumRed == 0 && iOwningTeam != TF_TEAM_BLUE )
+ {
+ iCappingTeam = TF_TEAM_BLUE;
+ }
+ else if ( iNumRed > 0 && iNumBlue == 0 && iOwningTeam != TF_TEAM_RED )
+ {
+ iCappingTeam = TF_TEAM_RED;
+ }
+ }
+
+ if ( iCappingTeam != TEAM_UNASSIGNED )
+ {
+ // Draw the number of cappers below the icon
+ int numPlayers = g_pObjectiveResource->GetNumPlayersInArea( iCP, iCappingTeam );
+ int requiredPlayers = g_pObjectiveResource->GetRequiredCappers( iCP, iCappingTeam );
+
+ if ( requiredPlayers > 1 )
+ {
+ numPlayers = MIN( numPlayers, requiredPlayers );
+
+ wchar_t wText[6];
+ _snwprintf( wText, sizeof(wText)/sizeof(wchar_t), L"%d", numPlayers );
+
+ int wide, tall;
+ surface()->GetTextSize( m_hIconFont, wText, wide, tall );
+
+ int x = pospanel.x-(wide/2);
+ int y = pospanel.y;
+
+ // match the offset that MapOverview uses
+ y += GetPixelOffset( scale ) + 4;
+
+ // draw black shadow text
+ surface()->DrawSetTextColor( 0, 0, 0, 255 );
+ surface()->DrawSetTextPos( x+1, y );
+ surface()->DrawPrintText( wText, wcslen(wText) );
+
+ // draw name in color
+ surface()->DrawSetTextColor( g_PR->GetTeamColor( iCappingTeam ) );
+ surface()->DrawSetTextPos( x, y );
+ surface()->DrawPrintText( wText, wcslen(wText) );
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::DrawHorizontalSwipe( Vector pos, int scale, int textureID, float flCapPercentage, bool bSwipeLeft )
+{
+ float flIconSize = scale * 2;
+ float width = ( flIconSize * flCapPercentage );
+
+ float uv1 = 0.0f;
+ float uv2 = 1.0f;
+
+ Vector2D uv11( uv1, uv2 );
+ Vector2D uv21( flCapPercentage, uv2 );
+ Vector2D uv22( flCapPercentage, uv1 );
+ Vector2D uv12( uv1, uv1 );
+
+ // reversing the direction of the swipe effect
+ if ( bSwipeLeft )
+ {
+ uv11.x = uv2 - flCapPercentage;
+ uv21.x = uv2;
+ uv22.x = uv2;
+ uv12.x = uv2 - flCapPercentage;
+ }
+
+ float flXPos = pos.x - scale;
+ float flYPos = pos.y - scale;
+
+ Vector upperLeft( flXPos, flYPos, 0 );
+ Vector upperRight( flXPos + width, flYPos, 0 );
+ Vector lowerRight( flXPos + width, flYPos + flIconSize, 0 );
+ Vector lowerLeft ( flXPos, flYPos + flIconSize, 0 );
+
+ /// reversing the direction of the swipe effect
+ if ( bSwipeLeft )
+ {
+ upperLeft.x = flXPos + flIconSize - width;
+ upperRight.x = flXPos + flIconSize;
+ lowerRight.x = flXPos + flIconSize;
+ lowerLeft.x = flXPos + flIconSize - width;
+ }
+
+ vgui::Vertex_t vert[4];
+
+ Vector2D pos0 = WorldToMap( upperLeft );
+ vert[0].Init( MapToPanel( pos0 ), uv11 );
+
+ Vector2D pos3 = WorldToMap( lowerLeft );
+ vert[1].Init( MapToPanel( pos3 ), uv12 );
+
+ Vector2D pos2 = WorldToMap( lowerRight );
+ vert[2].Init( MapToPanel( pos2 ), uv22 );
+
+ Vector2D pos1 = WorldToMap( upperRight );
+ vert[3].Init( MapToPanel( pos1 ), uv21 );
+
+ surface()->DrawSetColor( 255, 255, 255, 255 );
+ surface()->DrawSetTexture( textureID );
+ surface()->DrawTexturedPolygon( 4, vert );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::SetMap( const char * levelname )
+{
+ BaseClass::SetMap( levelname );
+
+ if ( m_nMapTextureID != -1 )
+ {
+ // we found a texture for this map
+ SetDisabled( false );
+ SetMode( m_nMode );
+ }
+ else
+ {
+ // we failed to load a map image
+ SetDisabled( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMapOverview::ShouldDraw( void )
+{
+ if ( IsDisabled() )
+ {
+ return false;
+ }
+
+ return BaseClass::ShouldDraw();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::Paint()
+{
+ UpdateMapOverlayTexture();
+
+ UpdateSizeAndPosition();
+
+ UpdateFollowEntity();
+
+ UpdateObjects();
+
+ UpdatePlayers();
+
+// UpdatePlayerTrails();
+
+ DrawMapTexture();
+
+ DrawMapOverlayTexture();
+
+// DrawMapPlayerTrails();
+
+ DrawObjects();
+
+ DrawMapPlayers();
+
+ DrawCamera();
+
+ Panel::Paint();
+}
+
+extern ConVar overview_alpha;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::DrawMapOverlayTexture()
+{
+ // now draw a box around the outside of this panel
+ int x0, y0, x1, y1;
+ int wide, tall;
+
+ GetSize( wide, tall );
+ x0 = 0; y0 = 0; x1 = wide - 2; y1 = tall - 2;
+
+ if ( m_nMapTextureOverlayID < 0 )
+ {
+ return;
+ }
+
+ Vertex_t points[4] =
+ {
+ Vertex_t( MapToPanel ( Vector2D(0,0) ), Vector2D(0,0) ),
+ Vertex_t( MapToPanel ( Vector2D(OVERVIEW_MAP_SIZE-1,0) ), Vector2D(1,0) ),
+ Vertex_t( MapToPanel ( Vector2D(OVERVIEW_MAP_SIZE-1,OVERVIEW_MAP_SIZE-1) ), Vector2D(1,1) ),
+ Vertex_t( MapToPanel ( Vector2D(0,OVERVIEW_MAP_SIZE-1) ), Vector2D(0,1) )
+ };
+
+ int alpha = 255.0f * overview_alpha.GetFloat(); clamp( alpha, 1, 255 );
+
+ surface()->DrawSetColor( 255,255,255, alpha );
+ surface()->DrawSetTexture( m_nMapTextureOverlayID );
+ surface()->DrawTexturedPolygon( 4, points );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMapOverview::UpdateMapOverlayTexture()
+{
+
+
+/*
+ char tempfile[MAX_PATH];
+ Q_snprintf( tempfile, sizeof( tempfile ), "overviews/%s_%s_%s_%s", levelname, roundname, capname, teamname );
+
+ // TODO release old texture ?
+
+ m_nMapTextureOverlayID = surface()->CreateNewTextureID();
+
+ //if we have not uploaded yet, lets go ahead and do so
+ surface()->DrawSetTextureFile( m_nMapTextureOverlayID, tempfile, true, false );
+
+ int wide, tall;
+
+ surface()->DrawGetTextureSize( m_nMapTextureOverlayID, wide, tall );
+
+ if ( wide != tall )
+ {
+ DevMsg( 1, "Error! CTFMapOverview::UpdateMapOverlayTexture: map overlay image must be a square.\n" );
+ m_nMapTextureOverlayID = -1;
+ return;
+ }
+*/
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_overview.h b/game/client/tf/vgui/tf_overview.h
new file mode 100644
index 0000000..e7c3a37
--- /dev/null
+++ b/game/client/tf/vgui/tf_overview.h
@@ -0,0 +1,80 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_OVERVIEW_H
+#define TF_OVERVIEW_H
+
+#include <mapoverview.h>
+#include "tf_shareddefs.h"
+
+class CTFMapOverview : public CMapOverview
+{
+ DECLARE_CLASS_SIMPLE( CTFMapOverview, CMapOverview );
+
+ CTFMapOverview( const char *pElementName );
+
+ int m_CameraIcons[MAX_TEAMS];
+ int m_CapturePoints[MAX_CONTROL_POINTS];
+
+ void ShowLargeMap( void );
+ void HideLargeMap( void );
+ void ToggleZoom( void );
+
+ void PlayerChat( int index );
+
+ void DrawQuad( Vector pos, int scale, float angle, int textureID, int alpha );
+ void DrawHorizontalSwipe( Vector pos, int scale, int textureID, float flCapPercentage, bool bSwipeLeft );
+ bool DrawCapturePoint( int iCP, MapObject_t *obj );
+
+ void SetDisabled( bool disabled ){ m_bDisabled = disabled; }
+ bool IsDisabled( void ){ return m_bDisabled; }
+
+ virtual void Paint();
+ virtual bool ShouldDraw( void );
+ virtual void VidInit( void );
+ virtual void SetMap( const char * map );
+
+protected:
+ virtual void SetMode(int mode);
+ virtual void InitTeamColorsAndIcons();
+ virtual void FireGameEvent( IGameEvent *event );
+ virtual void DrawCamera();
+ virtual void DrawMapPlayers();
+ virtual void Update();
+ virtual void UpdateSizeAndPosition();
+ virtual void UpdateMapOverlayTexture();
+
+ virtual void DrawMapOverlayTexture();
+
+ // rules that define if you can see a player on the overview or not
+ virtual bool CanPlayerBeSeen( MapPlayer_t *player );
+
+ void DrawVoiceIconForPlayer( int playerIndex );
+
+ virtual bool DrawIcon( MapObject_t *obj );
+
+protected:
+ void UpdateCapturePoints();
+
+private:
+ int m_iLastMode;
+
+ int m_iVoiceIcon;
+ int m_iChatIcon;
+
+ bool m_bDisabled;
+
+ float m_flPlayerChatTime[MAX_PLAYERS];
+
+ int m_nMapTextureOverlayID; // texture id for current overlay image (shown over the current overview image)
+
+#define TF_MAP_ZOOM_LEVELS 2
+};
+
+extern CTFMapOverview *GetTFOverview( void );
+
+#endif // TF_OVERVIEW_H
diff --git a/game/client/tf/vgui/tf_particlepanel.cpp b/game/client/tf/vgui/tf_particlepanel.cpp
new file mode 100644
index 0000000..8513dd1
--- /dev/null
+++ b/game/client/tf/vgui/tf_particlepanel.cpp
@@ -0,0 +1,556 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A panel that display particle systems
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include <KeyValues.h>
+#include <vgui/IScheme.h>
+#include <vgui/ISurface.h>
+#include <vgui_controls/EditablePanel.h>
+#include "vgui/IVGui.h"
+
+#include "tf_particlepanel.h"
+#include "matsys_controls/matsyscontrols.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "tier2/renderutils.h"
+#include "renderparm.h"
+
+using namespace vgui;
+
+
+CTFParticlePanel::ParticleEffect_t::ParticleEffect_t()
+ : m_bLoop( true )
+ , m_pParticleSystem( NULL )
+ , m_flLastTime( FLT_MAX )
+ , m_ParticleSystemName( NULL )
+ , m_bStartActivated( true )
+ , m_flScale( 1.f )
+ , m_flEndTime( FLT_MAX )
+ , m_nXPos( 0 )
+ , m_nYPos( 0 )
+ , m_Angles( 0.f, 0.f, 0.f )
+ , m_pParent( NULL )
+ , m_bForceStopped( false )
+ , m_bAutoDelete( false )
+ , m_bStarted( false )
+{}
+
+
+DECLARE_BUILD_FACTORY( CTFParticlePanel );
+//-----------------------------------------------------------------------------
+// Constructor, destructor
+//-----------------------------------------------------------------------------
+CTFParticlePanel::CTFParticlePanel( vgui::Panel *pParent, const char *pName )
+ : BaseClass( pParent, pName )
+{
+ m_Camera.m_flZNear = 3.0f;
+ m_Camera.m_flZFar = 16384.0f * 1.73205080757f;
+ m_Camera.m_flFOV = 30.0f;
+ m_Camera.m_origin = Vector(0,0,0);
+ m_Camera.m_angles = QAngle(0,0,0);
+
+ m_pLightmapTexture.Init( "//platform/materials/debug/defaultlightmap", "editor" );
+ m_DefaultEnvCubemap.Init( "editor/cubemap", "editor", true );
+}
+
+CTFParticlePanel::~CTFParticlePanel()
+{
+ m_pLightmapTexture.Shutdown();
+ m_DefaultEnvCubemap.Shutdown();
+ m_vecParticleEffects.PurgeAndDeleteElements();
+}
+
+
+void CTFParticlePanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pKVParticleEffects = inResourceData->FindKey( "ParticleEffects" );
+ if ( pKVParticleEffects )
+ {
+ FOR_EACH_SUBKEY( pKVParticleEffects, pKVEffect )
+ {
+ m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t();
+ ParticleEffect_t* pEffect = m_vecParticleEffects.Tail();
+
+ // get the position
+ int alignScreenWide = GetWide(), alignScreenTall = GetTall(); // screen dimensions used for pinning in splitscreen
+
+ int x, y;
+ GetPos(x, y);
+ const char *xstr = pKVEffect->GetString( "particle_xpos", NULL );
+ const char *ystr = pKVEffect->GetString( "particle_ypos", NULL );
+
+ if (xstr)
+ {
+ bool bRightAlign = false;
+ bool bCenterAlign = false;
+ // look for alignment flags
+ if (xstr[0] == 'r' || xstr[0] == 'R')
+ {
+ bRightAlign = true;
+ xstr++;
+ }
+ else if (xstr[0] == 'c' || xstr[0] == 'C')
+ {
+ bCenterAlign = true;
+ xstr++;
+ }
+
+ // get the value
+ x = atoi(xstr);
+ // scale the x up to our screen co-ords
+ if ( IsProportional() )
+ {
+ x = scheme()->GetProportionalScaledValueEx(GetScheme(), x);
+ }
+ // now correct the alignment
+ if ( bRightAlign )
+ {
+ x = alignScreenWide - x;
+ }
+ else if ( bCenterAlign )
+ {
+ x = (alignScreenWide / 2) + x;
+ }
+ }
+
+ if (ystr)
+ {
+ bool bBottomAlign = false;
+ bool bCenterAlign = false;
+ // look for alignment flags
+ if (ystr[0] == 'r' || ystr[0] == 'R')
+ {
+ bBottomAlign = true;
+ ystr++;
+ }
+ else if (ystr[0] == 'c' || ystr[0] == 'C')
+ {
+ bCenterAlign = true;
+ ystr++;
+ }
+ y = atoi(ystr);
+ if (IsProportional())
+ {
+ // scale the y up to our screen co-ords
+ y = scheme()->GetProportionalScaledValueEx(GetScheme(), y);
+ }
+ // now correct the alignment
+ if ( bBottomAlign )
+ {
+ y = alignScreenTall - y;
+ }
+ else if ( bCenterAlign )
+ {
+ y = (alignScreenTall / 2) + y;
+ }
+ }
+
+ pEffect->m_nXPos = x;
+ pEffect->m_nYPos = y;
+
+ pEffect->m_flScale = pKVEffect->GetFloat( "particle_scale", 1.f );
+ // Scale the scale factor the same way we do the XY position coordinates
+ if( IsProportional() )
+ {
+ int wide, tall;
+ surface()->GetScreenSize( wide, tall );
+
+ int proH, proW;
+ surface()->GetProportionalBase( proW, proH );
+ double scale = (double)tall / (double)proH;
+ pEffect->m_flScale *= scale;
+ }
+
+ pEffect->m_pParent = this;
+ pEffect->m_bLoop = pKVEffect->GetBool( "loop", true );
+ pEffect->m_bStartActivated = pKVEffect->GetBool( "start_activated", true );
+ pEffect->SetParticleSystem( pKVEffect->GetString( "particleName" ) );
+
+ // Read angles for the particle system
+ {
+ float x1,y1,z1;
+ const char* pszAngles = pKVEffect->GetString( "angles" );
+ if( *pszAngles )
+ {
+ if( pEffect->m_pParticleSystem && sscanf( pszAngles, "%f %f %f", &x1, &y1, &z1 ) == 3 )
+ {
+ pEffect->m_Angles = QAngle( x1, y1, z1 );
+ Quaternion q;
+ AngleQuaternion( pEffect->m_Angles , q );
+ pEffect->m_pParticleSystem->SetControlPointOrientation( 0, q );
+ }
+ }
+ }
+
+ pEffect->SetControlPointValue( 0, Vector(0,0,0) );
+ // Read all control point values
+ const char* pszControlPoint = NULL;
+ int nControlPointNumber = 0;
+ do
+ {
+ pszControlPoint = pKVEffect->GetString( VarArgs("control_point%d", nControlPointNumber), "" );
+ if ( *pszControlPoint )
+ {
+ float x2,y2,z2;
+ if (sscanf(pszControlPoint, "%f %f %f", &x2, &y2, &z2 ) == 3)
+ {
+ pEffect->SetControlPointValue( nControlPointNumber, Vector( x2, y2, z2 ) );
+ }
+ }
+
+ ++nControlPointNumber;
+ }
+ while( *pszControlPoint );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Scheme
+//-----------------------------------------------------------------------------
+void CTFParticlePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetMouseInputEnabled( false );
+ SetKeyBoardInputEnabled( false );
+}
+
+void CTFParticlePanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "start", ARRAYSIZE("start") - 1 ) )
+ {
+ // Check if they specified a particular one to turn on
+ const char* pszNum = command + ARRAYSIZE( "start" ) - 1;
+ int nIndex = m_vecParticleEffects.InvalidIndex();
+
+ if( pszNum && pszNum[0] )
+ {
+ nIndex = atoi(pszNum);
+ Assert( nIndex >= 0 && nIndex < m_vecParticleEffects.Count() );
+ }
+
+ FOR_EACH_VEC( m_vecParticleEffects, i )
+ {
+ if ( nIndex != m_vecParticleEffects.InvalidIndex() && i != nIndex )
+ continue;
+
+ ParticleEffect_t* pEffect = m_vecParticleEffects[ i ];
+
+ if( !pEffect->m_pParticleSystem )
+ {
+ pEffect->SetParticleSystem( pEffect->m_ParticleSystemName );
+ }
+
+ if( !pEffect->m_pParticleSystem )
+ continue;
+
+ pEffect->StartupParticleCollection();
+ pEffect->m_pParticleSystem->StartEmission();
+ pEffect->m_bForceStopped = false;
+ }
+ }
+ else if ( !Q_strnicmp( command, "stop", ARRAYSIZE("stop") - 1 ) )
+ {
+ // Check if they specified a specific one to turn off
+ const char* pszNum = command + ARRAYSIZE( "start" ) - 1;
+ if( pszNum && pszNum[0] )
+ {
+ int iIndex = atoi(pszNum);
+ Assert( iIndex >= 0 && iIndex < m_vecParticleEffects.Count() );
+
+ ParticleEffect_t* pEffect = m_vecParticleEffects[iIndex];
+ if( pEffect->m_pParticleSystem )
+ {
+ pEffect->m_pParticleSystem->StopEmission();
+ pEffect->m_bForceStopped = true;
+ }
+ }
+ else
+ {
+ // Turn them ALL off
+ FOR_EACH_VEC( m_vecParticleEffects, i )
+ {
+ ParticleEffect_t* pEffect = m_vecParticleEffects[ i ];
+ if( pEffect->m_pParticleSystem )
+ {
+ pEffect->m_pParticleSystem->StopEmission();
+ pEffect->m_bForceStopped = true;
+ }
+ }
+ }
+ }
+}
+
+void CTFParticlePanel::FireParticleEffect( const char *pszName, int xPos, int yPos, float flScale, bool bLoop, float flEndTime )
+{
+ m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t();
+ ParticleEffect_t* pEffect = m_vecParticleEffects.Tail();
+
+ int iParentAbsX, iParentAbsY;
+ vgui::ipanel()->GetAbsPos( GetParent()->GetVPanel(), iParentAbsX, iParentAbsY );
+
+ pEffect->m_pParent = this;
+ pEffect->m_nXPos = xPos - iParentAbsX;
+ pEffect->m_nYPos = yPos - iParentAbsY;
+ pEffect->m_flScale = flScale;
+ pEffect->m_bLoop = bLoop;
+ pEffect->m_bAutoDelete = true; // This will get automatically deleted once it stops
+ pEffect->m_bStartActivated = true;
+ pEffect->m_flEndTime = gpGlobals->curtime + flEndTime;
+
+ if( IsProportional() )
+ {
+ int wide, tall;
+ surface()->GetScreenSize( wide, tall );
+
+ int proH, proW;
+ surface()->GetProportionalBase( proW, proH );
+ double scale = (double)tall / (double)proH;
+ pEffect->m_flScale *= scale;
+ }
+
+ for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i )
+ {
+ pEffect->SetControlPointValue( i, Vector( 0, 0, 10.0f * i ) );
+ }
+ pEffect->SetParticleSystem( pszName );
+}
+
+
+static bool IsValidHierarchy( CParticleCollection *pCollection )
+{
+ if ( !pCollection->IsValid() )
+ return false;
+
+ for( CParticleCollection *pChild = pCollection->m_Children.m_pHead; pChild; pChild = pChild->m_pNext )
+ {
+ if ( !IsValidHierarchy( pChild ) )
+ return false;
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Simulate the particle system
+//-----------------------------------------------------------------------------
+void CTFParticlePanel::OnTick()
+{
+ BaseClass::OnTick();
+
+ float flTime = engine->Time();
+
+ bool bAnyActive = false;
+ // Update all particles
+ FOR_EACH_VEC_BACK( m_vecParticleEffects, i )
+ {
+ bAnyActive |= m_vecParticleEffects[i]->Update( flTime );
+ // If this effect is done and should auto-delete, then now is when we delete
+ if( m_vecParticleEffects[i]->m_pParticleSystem == NULL && m_vecParticleEffects[i]->m_bAutoDelete )
+ {
+ delete m_vecParticleEffects[i];
+ m_vecParticleEffects.FastRemove( i );
+ }
+ }
+
+ if ( !bAnyActive )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+}
+
+
+void CTFParticlePanel::Paint()
+{
+ // This needs calling to reset various counters.
+ g_pParticleSystemMgr->SetLastSimulationTime( gpGlobals->curtime );
+
+ // No particles? Do nothing.
+ if( m_vecParticleEffects.Count() == 0 )
+ return;
+
+ int screenW, screenH;
+ vgui::surface()->GetScreenSize( screenW, screenH );
+
+ vgui::MatSystemSurface()->Begin3DPaint( 0, 0, screenW, screenH, false );
+
+ VMatrix view, projection;
+ ComputeViewMatrix( &view, m_Camera );
+ ComputeProjectionMatrix( &projection, m_Camera, screenW, screenH );
+
+ CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
+
+ pRenderContext->CullMode( MATERIAL_CULLMODE_CCW );
+ pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false );
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->LoadIdentity( );
+
+ pRenderContext->MatrixMode( MATERIAL_VIEW );
+ pRenderContext->LoadMatrix( view );
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->LoadMatrix( projection );
+
+ int iXOffset, iYOffset;
+ vgui::ipanel()->GetAbsPos( GetVPanel(), iXOffset, iYOffset );
+ if ( iXOffset > 0 )
+ iXOffset = 0;
+ if ( iYOffset > 0 )
+ iYOffset = 0;
+
+ float flXScale = 1.f;
+ if ( GetWide() > screenW )
+ flXScale = (float)screenW / GetWide();
+ float flYScale = 1.f;
+ if ( GetTall() > screenH )
+ flYScale = (float)screenH / GetTall();
+
+ FOR_EACH_VEC( m_vecParticleEffects, i )
+ {
+ m_vecParticleEffects[i]->Paint( pRenderContext, iXOffset, iYOffset, flXScale, flYScale, screenW, screenH );
+ }
+
+ pRenderContext->CullMode( MATERIAL_CULLMODE_CW );
+
+ vgui::MatSystemSurface()->End3DPaint();
+}
+
+bool CTFParticlePanel::ParticleEffect_t::Update( float flTime )
+{
+ if ( !m_pParticleSystem || !m_bStarted )
+ return false;
+
+ if ( m_flLastTime == FLT_MAX )
+ {
+ m_flLastTime = flTime;
+ }
+
+ float flDt = flTime - m_flLastTime;
+ m_flLastTime = flTime;
+
+ Quaternion q;
+ AngleQuaternion( m_Angles, q );
+
+ for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i )
+ {
+ if ( !m_pParticleSystem->ReadsControlPoint( i ) )
+ continue;
+
+ m_pParticleSystem->SetControlPoint( i, m_pControlPointValue[i] );
+ m_pParticleSystem->SetControlPointOrientation( i, q );
+ m_pParticleSystem->SetControlPointParent( i, i );
+ }
+
+ // Restart the particle system if it's finished
+ bool bIsInvalid = !IsValidHierarchy( m_pParticleSystem );
+
+ if ( !bIsInvalid )
+ {
+ m_pParticleSystem->Simulate( flDt, false );
+ }
+
+ // Past our end time?
+ bool bEnd = gpGlobals->curtime >= m_flEndTime;
+
+ if ( m_pParticleSystem->IsFinished() || bIsInvalid || bEnd )
+ {
+ delete m_pParticleSystem;
+ m_pParticleSystem = NULL;
+
+ // Loop if we're supposed to
+ if ( m_bLoop && m_ParticleSystemName.Length() && !m_bForceStopped )
+ {
+ m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( m_ParticleSystemName );
+ }
+
+ if ( bIsInvalid && m_pParent )
+ {
+ m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) );
+ }
+ m_flLastTime = FLT_MAX;
+ }
+
+ return m_pParticleSystem != NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Startup, shutdown particle collection
+//-----------------------------------------------------------------------------
+void CTFParticlePanel::ParticleEffect_t::StartupParticleCollection()
+{
+ if ( m_pParticleSystem && m_pParent )
+ {
+ vgui::ivgui()->AddTickSignal( m_pParent->GetVPanel(), 0 );
+ }
+ m_flLastTime = FLT_MAX;
+ m_bStarted = true;
+}
+
+void CTFParticlePanel::ParticleEffect_t::ShutdownParticleCollection()
+{
+ if ( m_pParticleSystem && m_pParent )
+ {
+ delete m_pParticleSystem;
+ m_pParticleSystem = NULL;
+ }
+ m_bStarted = false;
+}
+
+//-----------------------------------------------------------------------------
+// Set the particle system to draw
+//-----------------------------------------------------------------------------
+void CTFParticlePanel::ParticleEffect_t::SetParticleSystem( const char* pszParticleSystemName )
+{
+ ShutdownParticleCollection();
+
+ if( !g_pParticleSystemMgr->IsParticleSystemDefined( pszParticleSystemName ) )
+ {
+ AssertMsg1( false, "%s is not a valid particle system name", pszParticleSystemName );
+ return;
+ }
+ m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( pszParticleSystemName );
+ m_ParticleSystemName = pszParticleSystemName;
+
+ m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) );
+
+ if( m_bStartActivated )
+ {
+ StartupParticleCollection();
+ }
+}
+
+
+void CTFParticlePanel::ParticleEffect_t::Paint( CMatRenderContextPtr& pRenderContext, int iXOffset, int iYOffset, float flXScale, float flYScale, int screenW, int screenH )
+{
+ if ( !m_pParticleSystem || !m_bStarted )
+ return;
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity();
+
+ pRenderContext->Ortho( 0, 0, screenW, screenH, -9999, 9999 );
+
+ pRenderContext->Translate( flXScale * ( m_nXPos + iXOffset ), screenH - flYScale * ( m_nYPos + iYOffset ), 0.f );
+ pRenderContext->Scale( m_flScale, m_flScale, m_flScale );
+
+ // Render Particles
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PushMatrix();
+ pRenderContext->LoadIdentity( );
+
+ m_pParticleSystem->Render( pRenderContext );
+
+ pRenderContext->MatrixMode( MATERIAL_MODEL );
+ pRenderContext->PopMatrix();
+
+ pRenderContext->MatrixMode( MATERIAL_PROJECTION );
+ pRenderContext->PopMatrix();
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_particlepanel.h b/game/client/tf/vgui/tf_particlepanel.h
new file mode 100644
index 0000000..9eccae4
--- /dev/null
+++ b/game/client/tf/vgui/tf_particlepanel.h
@@ -0,0 +1,84 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A panel that display particle systems
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_PARTICLEPANEL_H
+#define TF_PARTICLEPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tier2/camerautils.h"
+
+class CTFParticlePanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFParticlePanel, vgui::EditablePanel );
+public:
+ // constructor, destructor
+ CTFParticlePanel( vgui::Panel *pParent, const char *pName );
+ virtual ~CTFParticlePanel();
+
+ virtual void OnTick();
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ void FireParticleEffect( const char *pszName, int xPos, int yPos, float flScale, bool bLoop, float flEndTime = FLT_MAX );
+private:
+
+ // paint it!
+ virtual void Paint() OVERRIDE;
+
+private:
+
+ // Class to contain all of the per-instance particle system data
+ struct ParticleEffect_t
+ {
+ ParticleEffect_t();
+
+ // Shutdown, startup particle collection
+ void StartupParticleCollection();
+ void ShutdownParticleCollection();
+
+ // Accessor for control point values
+ const Vector& GetControlPointValue( int nControlPoint ) const { return m_pControlPointValue[ nControlPoint ]; };
+ void SetControlPointValue( int nControlPoint, const Vector &value ) { m_pControlPointValue[ nControlPoint ] = value; }
+ // Set the particle system to draw
+ void SetParticleSystem( const char* pszParticleSystemName );
+
+ bool Update( float flTime );
+ void Paint( CMatRenderContextPtr& pRenderContext, int iXOffset, int iYOffset, float flXScale, float flYScale, int screenW, int screenH );
+
+ Vector m_pControlPointValue[MAX_PARTICLE_CONTROL_POINTS];
+ CParticleCollection *m_pParticleSystem;
+ CUtlString m_ParticleSystemName;
+ float m_flLastTime;
+ float m_flScale;
+ float m_flEndTime;
+ int m_nXPos;
+ int m_nYPos;
+ QAngle m_Angles;
+ bool m_bStartActivated; // Start the effect immediately?
+ bool m_bLoop; // Loop the effect?
+ bool m_bForceStopped;
+ bool m_bAutoDelete;
+ bool m_bStarted;
+
+ Panel* m_pParent;
+ };
+
+ CUtlVector< ParticleEffect_t* > m_vecParticleEffects;
+
+ // A texture to use for a lightmap
+ CTextureReference m_pLightmapTexture;
+
+ // The default env_cubemap
+ CTextureReference m_DefaultEnvCubemap;
+
+ Camera_t m_Camera;
+};
+
+#endif // TF_PARTICLEPANEL_H
diff --git a/game/client/tf/vgui/tf_ping_panel.cpp b/game/client/tf/vgui/tf_ping_panel.cpp
new file mode 100644
index 0000000..359deaa
--- /dev/null
+++ b/game/client/tf/vgui/tf_ping_panel.cpp
@@ -0,0 +1,286 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_ping_panel.h"
+
+#include "vgui_controls/ProgressBar.h"
+#include "vgui_controls/AnimationController.h"
+
+#include "tf_gc_client.h"
+
+#include "clientmode_tf.h"
+
+#include "tf_matchmaking_shared.h"
+
+static void OnConVarChangeCustomPingTolerance( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ if ( GTFGCClientSystem() )
+ {
+ // Otherwise the client system will do this when it starts
+ GTFGCClientSystem()->UpdateCustomPingTolerance();
+ }
+}
+
+ConVar tf_custom_ping_enabled( "tf_custom_ping_enabled", "0", FCVAR_ARCHIVE, "",
+ false, 0.f, false, 0.f, OnConVarChangeCustomPingTolerance );
+ConVar tf_custom_ping( "tf_custom_ping", "100", FCVAR_ARCHIVE, "",
+ true, (float)CUSTOM_PING_TOLERANCE_MIN, true, (float)CUSTOM_PING_TOLERANCE_MAX,
+ OnConVarChangeCustomPingTolerance );
+
+#ifdef STAGING_ONLY
+ConVar tf_custom_ping_add_random_datacenters( "tf_custom_ping_add_random_datacenters", "0" );
+#endif // STAGING_ONLY
+
+CTFPingPanel::CTFPingPanel( Panel* pPanel, const char *pszName, EMatchGroup eMatchGroup )
+ : EditablePanel( pPanel, pszName ),
+ m_eMatchGroup( eMatchGroup )
+{
+ SetProportional( true );
+
+ m_pMainContainer = new EditablePanel( this, "MainContainer" );
+ m_pCheckButton = new CheckButton( m_pMainContainer, "CheckButton", "" );
+ m_pCurrentPingLabel = new Label( m_pMainContainer, "CurrentPingLabel", "" );
+ m_pPingSlider = new CCvarSlider( m_pMainContainer, "PingSlider" );
+
+ ListenForGameEvent( "ping_updated" );
+}
+
+
+CTFPingPanel::~CTFPingPanel()
+{
+ CleanupPingPanels();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/MatchMakingPingPanel.res" );
+
+ CleanupPingPanels();
+
+ m_pCheckButton->AddActionSignalTarget( this );
+ m_pPingSlider->AddActionSignalTarget( this );
+
+ CScrollableList *pDataCenterList = FindControl< CScrollableList >( "DataCenterList", true );
+
+ static const wchar_t *s_pwszPingFormat = L"%ls (%d ms)";
+
+ if ( pDataCenterList && GTFGCClientSystem()->BHavePingData() )
+ {
+ auto pingData = GTFGCClientSystem()->GetPingData();
+ const auto& dictDataCenterPopulations = GTFGCClientSystem()->GetDataCenterPopulationRatioDict( m_eMatchGroup );
+
+ bool bTesting = false;
+#ifdef STAGING_ONLY
+ if ( tf_custom_ping_add_random_datacenters.GetBool() )
+ {
+ bTesting = true;
+ const char* pszDataCenterNames[] = { "eat", "lax","iad","atl","gru","scl","lim","lux","vie","sto",
+ "mad","sgp","hkg","tyo","syd","dxb","bom","maa","ord","waw","jhb" };
+
+ CUniformRandomStream randomstream;
+ randomstream.SetSeed( tf_custom_ping_add_random_datacenters.GetInt() );
+ for( int i=0; i < ARRAYSIZE( pszDataCenterNames ); ++i )
+ {
+ auto pNewPingData = pingData.add_pingdata();
+ pNewPingData->set_name( pszDataCenterNames[ i ] );
+ pNewPingData->set_ping( randomstream.RandomInt( 0, 250 ) );
+ pNewPingData->set_ping_status( CMsgGCDataCenterPing_Update_Status_Normal );
+ }
+ }
+#endif
+
+ // for each ping data, check for intersection with data center population from MMStats
+ for ( int iPing=0; iPing<pingData.pingdata_size(); ++iPing )
+ {
+ auto pingEntry = pingData.pingdata( iPing );
+ if ( pingEntry.ping_status() != CMsgGCDataCenterPing_Update_Status_Normal )
+ continue;
+
+ const char *pszPingFromDataCenterName = pingEntry.name().c_str();
+ auto dictIndex = dictDataCenterPopulations.Find( pszPingFromDataCenterName );
+ // found intersection. add a population health panel
+ if ( dictIndex != dictDataCenterPopulations.InvalidIndex() || bTesting)
+ {
+ // Load control settings
+ EditablePanel* pDataCenterPopulationPanel = new EditablePanel( pDataCenterList, "DataCenterPopulationPanel" );
+ pDataCenterPopulationPanel->LoadControlSettings( "resource/ui/MatchMakingDataCenterPopulationPanel.res" );
+ pDataCenterPopulationPanel->SetAutoDelete( false );
+ pDataCenterList->ResetScrollAmount();
+ pDataCenterList->InvalidateLayout();
+
+ // Update label
+ wchar_t wszDataCenterName[ 128 ];
+ wchar_t* pwszLocalizedDataCenterName = g_pVGuiLocalize->Find( CFmtStr( "#TF_DataCenter_%s", pszPingFromDataCenterName ) );
+ if ( pwszLocalizedDataCenterName )
+ {
+ V_wcsncpy( wszDataCenterName, pwszLocalizedDataCenterName, sizeof( wszDataCenterName ) );
+ }
+ else
+ {
+ // Fallback is no token. If you hit this, go add a string for this data center in tf_english!
+ Assert( false );
+ g_pVGuiLocalize->ConvertANSIToUnicode( pszPingFromDataCenterName, wszDataCenterName, sizeof( wszDataCenterName ) );
+ }
+
+ wchar_t wszLabelText[ 128 ];
+ V_snwprintf( wszLabelText, sizeof( wszLabelText ), s_pwszPingFormat, wszDataCenterName, pingEntry.ping() );
+
+ pDataCenterPopulationPanel->SetDialogVariable( "datacenter_name", wszLabelText );
+
+ PingPanelInfo panelInfo;
+ panelInfo.m_pPanel = pDataCenterPopulationPanel;
+ panelInfo.m_flPopulationRatio = bTesting ? RandomFloat( 0.f, 1.f ) : dictDataCenterPopulations[dictIndex];
+ panelInfo.m_nPing = pingEntry.ping();
+ m_vecDataCenterPingPanels.AddToTail( panelInfo );
+ }
+ }
+ }
+
+ struct PingPanelInfoSorter
+ {
+ static int SortPingPanelInfo( const PingPanelInfo* a, const PingPanelInfo* b )
+ {
+ return a->m_nPing - b->m_nPing;
+ }
+ };
+ m_vecDataCenterPingPanels.Sort( &PingPanelInfoSorter::SortPingPanelInfo );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ m_pCheckButton->SetSelected( tf_custom_ping_enabled.GetBool() );
+
+ FOR_EACH_VEC( m_vecDataCenterPingPanels, i )
+ {
+ const PingPanelInfo& info = m_vecDataCenterPingPanels[i];
+ int iTall = info.m_pPanel->GetTall();
+ int iYGap = i > 0 ? m_iDataCenterYSpace : 0;
+ int iXPos = info.m_pPanel->GetXPos();
+ info.m_pPanel->SetPos( iXPos, m_iDataCenterY + iYGap + i * iTall );
+
+ // Update bars with latest health data
+ ProgressBar* pProgress = info.m_pPanel->FindControl< ProgressBar >( "HealthProgressBar", true );
+ if ( pProgress )
+ {
+ auto healthData = GTFGCClientSystem()->GetHealthBracketForRatio( info.m_flPopulationRatio );
+
+ pProgress->MakeReadyForUse();
+ pProgress->SetProgress( healthData.m_flRatio );
+ pProgress->SetFgColor( healthData.m_colorBar );
+ }
+ }
+
+ UpdateCurrentPing();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "close" ) )
+ {
+ MarkForDeletion();
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::FireGameEvent( IGameEvent *event )
+{
+ const char *pszEventName = event->GetName();
+ if ( FStrEq( pszEventName, "ping_updated" ) )
+ {
+ InvalidateLayout( true, true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::CleanupPingPanels()
+{
+ FOR_EACH_VEC( m_vecDataCenterPingPanels, i )
+ {
+ m_vecDataCenterPingPanels[i].m_pPanel->MarkForDeletion();
+ }
+
+ m_vecDataCenterPingPanels.Purge();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::UpdateCurrentPing()
+{
+ bool bUsePingLimit = m_pCheckButton->IsSelected();
+ int nLowestDataCenterPing = m_vecDataCenterPingPanels.Count() ? m_vecDataCenterPingPanels[0].m_nPing : 0;
+ int nCurrentPingLimit = MAX( (int)m_pPingSlider->GetSliderValue(), nLowestDataCenterPing );
+ m_pCurrentPingLabel->SetText( bUsePingLimit ? CFmtStr( "Ping Limit: %d", nCurrentPingLimit ) : "Ping Limit: AUTO" );
+
+ FOR_EACH_VEC( m_vecDataCenterPingPanels, i )
+ {
+ const PingPanelInfo& info = m_vecDataCenterPingPanels[i];
+
+ int bHighLight = !bUsePingLimit || info.m_nPing <= nCurrentPingLimit;
+ if ( bHighLight )
+ {
+ g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( info.m_pPanel, "HealthProgressBar_NotSelected" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( info.m_pPanel, "HealthProgressBar_Selected" );
+ }
+ else
+ {
+ g_pClientMode->GetViewportAnimationController()->StopAnimationSequence( info.m_pPanel, "HealthProgressBar_Selected" );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( info.m_pPanel, "HealthProgressBar_NotSelected" );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::OnCheckButtonChecked( vgui::Panel *panel )
+{
+ if ( m_pCheckButton == panel )
+ {
+ tf_custom_ping_enabled.SetValue( m_pCheckButton->IsSelected() );
+ }
+
+ m_pPingSlider->SetVisible( tf_custom_ping_enabled.GetBool() );
+
+ UpdateCurrentPing();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPingPanel::OnSliderMoved()
+{
+ UpdateCurrentPing();
+}
diff --git a/game/client/tf/vgui/tf_ping_panel.h b/game/client/tf/vgui/tf_ping_panel.h
new file mode 100644
index 0000000..e9e6102
--- /dev/null
+++ b/game/client/tf/vgui/tf_ping_panel.h
@@ -0,0 +1,58 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_PING_PANEL_H
+#define TF_PING_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/EditablePanel.h>
+#include <../common/GameUI/cvarslider.h>
+
+using namespace vgui;
+
+class CTFPingPanel : public EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CTFPingPanel, EditablePanel )
+public:
+ CTFPingPanel( Panel* pPanel, const char *pszName, EMatchGroup eMatchGroup );
+ ~CTFPingPanel();
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+private:
+ void CleanupPingPanels();
+ void UpdateCurrentPing();
+
+ MESSAGE_FUNC_PTR( OnCheckButtonChecked, "CheckButtonChecked", panel );
+ MESSAGE_FUNC( OnSliderMoved, "SliderMoved" );
+
+ CPanelAnimationVarAliasType( int, m_iDataCenterY, "datacenter_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iDataCenterYSpace, "datacenter_y_space", "0", "proportional_int" );
+
+ EditablePanel *m_pMainContainer;
+ CheckButton *m_pCheckButton;
+ Label *m_pCurrentPingLabel;
+ CCvarSlider *m_pPingSlider;
+
+ struct PingPanelInfo
+ {
+ EditablePanel *m_pPanel;
+ float m_flPopulationRatio;
+ int m_nPing;
+ };
+ CUtlVector< PingPanelInfo > m_vecDataCenterPingPanels;
+
+ EMatchGroup m_eMatchGroup;
+};
+
+#endif // TF_PING_PANEL_H
diff --git a/game/client/tf/vgui/tf_playermodelpanel.cpp b/game/client/tf/vgui/tf_playermodelpanel.cpp
new file mode 100644
index 0000000..cb49453
--- /dev/null
+++ b/game/client/tf/vgui/tf_playermodelpanel.cpp
@@ -0,0 +1,2893 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_shareddefs.h"
+#include "tf_playermodelpanel.h"
+#include "tf_classdata.h"
+#include "tf_item_inventory.h"
+#include "vgui/IVGui.h"
+#include "game_item_schema.h"
+#include "econ_item_system.h"
+#include "animation.h"
+#include "choreoscene.h"
+#include "choreoevent.h"
+#include "choreoactor.h"
+#include "choreochannel.h"
+#include "scenefilecache/ISceneFileCache.h"
+#include "c_sceneentity.h"
+#include "c_baseflex.h"
+#include "sentence.h"
+#include "engine/IEngineSound.h"
+#include "c_tf_player.h"
+#include "tier2/renderutils.h"
+#include "bone_setup.h"
+#include "halloween/tf_weapon_spellbook.h"
+#include "matsys_controls/matsyscontrols.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+DECLARE_BUILD_FACTORY( CTFPlayerModelPanel );
+
+char g_szSceneTmpName[256];
+
+static bool IsTauntItem( GameItemDefinition_t *pItemDef, const int iTeam, const int iClass, const char **ppSequence = NULL, const char **ppRequiredItem = NULL, const char **ppScene = NULL )
+{
+ CTFTauntInfo *pTauntData = pItemDef->GetTauntData();
+ if ( pTauntData )
+ {
+ if ( ppScene )
+ {
+ int iTauntIndex = RandomInt( 0, pTauntData->GetIntroSceneCount( iClass ) - 1 );
+ *ppScene = pTauntData->GetIntroScene( iClass, iTauntIndex );
+ }
+
+ if ( ppRequiredItem )
+ {
+ *ppRequiredItem = pTauntData->GetProp( iClass );
+ }
+
+ return true;
+ }
+
+ for ( int i=0; i<pItemDef->GetNumAnimations( iTeam ); ++i )
+ {
+ animation_on_wearable_t* pAnim = pItemDef->GetAnimationData( iTeam, i );
+ if ( pAnim && pAnim->pszActivity && !Q_stricmp( pAnim->pszActivity, "taunt_concept" ) )
+ {
+ // If we have a scene, use it first
+ const char *pszScene = pAnim->pszScene;
+ if ( pszScene && (iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS) )
+ {
+ if ( ppScene )
+ {
+ Q_snprintf( g_szSceneTmpName, sizeof(g_szSceneTmpName), "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[iClass], pszScene );
+ *ppScene = g_szSceneTmpName;
+ }
+ }
+
+ const char *pszSequence = pAnim->pszSequence;
+ if ( pszSequence )
+ {
+ if ( ppSequence )
+ {
+ *ppSequence = pszSequence;
+ }
+ if ( ppRequiredItem )
+ {
+ *ppRequiredItem = pAnim->pszRequiredItem;
+ }
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerModelPanel::CTFPlayerModelPanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName ),
+ m_LocalToGlobal( 0, 0, FlexSettingLessFunc )
+{
+ m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
+ m_iCurrentSlotIndex = -1;
+
+ m_nBody = 0;
+ m_pHeldItem = NULL;
+ m_iTeam = TF_TEAM_RED;
+ m_bZoomedToHead = false;
+ m_pszVCD = NULL;
+ m_pszWeaponEntityRequired = NULL;
+ m_bLoopVCD = true;
+ m_bVCDFileNameOnly = true;
+
+ InitPhonemeMappings();
+
+ m_pScene = NULL;
+ ClearScene();
+ memset( m_flexWeight, 0, sizeof( m_flexWeight ) );
+
+ SetIgnoreDoubleClick( true );
+
+ for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
+ {
+ m_aParticleSystems[i] = NULL;
+ }
+
+ m_bPlaySparks = false;
+ m_pszEyeGlowParticleName[0] = '\0';
+ m_bDrawActionSlotEffects = false;
+ m_bDrawTauntParticles = false;
+ m_bIsRobot = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerModelPanel::~CTFPlayerModelPanel( void )
+{
+ m_vecItemsLoaded.PurgeAndDeleteElements();
+ m_ItemsToCarry.PurgeAndDeleteElements();
+
+ for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[i] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_angPlayerOrg = m_angPlayer;
+
+ static ConVarRef cl_hud_minmode( "cl_hud_minmode", true );
+ if ( cl_hud_minmode.IsValid() && cl_hud_minmode.GetBool() )
+ {
+ inResourceData->ProcessResolutionKeys( "_minmode" );
+ }
+
+ // custom class data
+ m_customClassData.Purge();
+ KeyValues *pCustomData = inResourceData->FindKey( "customclassdata" );
+ if ( pCustomData )
+ {
+ for ( KeyValues *pData = pCustomData->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
+ {
+ CustomClassData_t data;
+ data.m_flFOV = pData->GetFloat( "fov" );
+ data.m_vPosition.Init( pData->GetFloat( "origin_x" ), pData->GetFloat( "origin_y" ), pData->GetFloat( "origin_z" ) );
+ data.m_vAngles.Init( pData->GetFloat( "angles_x" ), pData->GetFloat( "angles_y" ), pData->GetFloat( "angles_z" ) );
+ m_customClassData.AddToTail( data );
+ }
+
+ Assert( m_customClassData.Count() == TF_LAST_NORMAL_CLASS );
+ }
+
+ // always allow particle for this panel
+ m_bUseParticle = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bIsRobot, bool bForceRefresh /*= false*/ )
+{
+ if ( m_bIsRobot != bIsRobot )
+ {
+ bForceRefresh = true;
+ }
+ m_bIsRobot = bIsRobot;
+
+ if ( m_iCurrentClassIndex == iClass && !bForceRefresh )
+ return;
+
+ if ( m_bZoomedToHead )
+ {
+ ToggleZoom();
+ }
+
+ m_iCurrentClassIndex = iClass;
+ ClearScene();
+
+ if ( IsValidTFPlayerClass( m_iCurrentClassIndex ) )
+ {
+ if ( bIsRobot )
+ {
+ SetMDL( g_szPlayerRobotModels[ m_iCurrentClassIndex ] );
+ }
+ else
+ {
+ TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex );
+ SetMDL( pData->GetModelName() );
+ }
+
+ HoldFirstValidItem();
+
+ // set custom class data
+ if ( m_customClassData.IsValidIndex( m_iCurrentClassIndex ) )
+ {
+ SetCameraFOV( m_customClassData[m_iCurrentClassIndex].m_flFOV );
+ m_vecPlayerPos = m_customClassData[m_iCurrentClassIndex].m_vPosition;
+ m_angPlayer = m_customClassData[m_iCurrentClassIndex].m_vAngles;
+ }
+ else
+ {
+ m_angPlayer = m_angPlayerOrg;
+ }
+ }
+ else
+ {
+ SetMDL( MDLHANDLE_INVALID );
+ RemoveAdditionalModels();
+ }
+
+ InitPhonemeMappings();
+
+ SetTeam( TF_TEAM_RED );
+
+ m_nBody = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::HoldFirstValidItem( void )
+{
+ RemoveAdditionalModels();
+
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return;
+
+ int iDesiredSlot = -1;
+
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
+ if ( !bIsTauntItem )
+ {
+ if ( pItem->GetStaticData()->IsAWearable() )
+ continue;
+ if ( pItem->GetAnimationSlot() == -2 )
+ continue;
+ }
+
+ // Found a weapon. Wield it.
+ iDesiredSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ break;
+ }
+
+ if ( iDesiredSlot != -1 )
+ {
+ UpdateHeldItem( iDesiredSlot );
+ return;
+ }
+
+ // If we didn't find a weapon to wield, we wield the class's base primary weapon
+ CEconItemView *pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_PRIMARY );
+ if ( !pItem || !pItem->IsValid() )
+ {
+ // Some classes only have secondary weapons. Fall back to that.
+ pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_SECONDARY );
+ }
+
+ if ( pItem && pItem->IsValid() )
+ {
+ SwitchHeldItemTo( pItem );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::HoldItemInSlot( int iSlot )
+{
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return false;
+
+ return UpdateHeldItem( iSlot );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::HoldItem( int iItemNumber )
+{
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return false;
+
+ if ( iItemNumber >= m_ItemsToCarry.Count() )
+ return false;
+
+ CEconItemView *pItem = m_ItemsToCarry[iItemNumber];
+
+ bool bIsTauntitem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
+
+ // Ignore requests to equip wearables, because they're always equipped
+ // Also ignore requests to equip non-wearables that are never actively equipped
+ if ( bIsTauntitem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) )
+ {
+ SwitchHeldItemTo( pItem );
+ return true;
+ }
+
+ // If we were trying to switch to a new item, and it's not valid, stick to our current
+ if ( pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) != m_iCurrentSlotIndex )
+ {
+ UpdateHeldItem( m_iCurrentSlotIndex );
+ return false;
+ }
+
+ // We were trying to stay on the current weapon, and it's not valid. Find anything.
+ HoldFirstValidItem();
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot )
+{
+ m_pHeldItem = NULL;
+
+ CEconItemView *pItem = GetItemInSlot( iDesiredSlot );
+ if ( pItem )
+ {
+ bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
+ // Ignore requests to equip wearables, because they're always equipped
+ // Also ignore requests to equip non-wearables that are never actively equipped
+ if ( bIsTauntItem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) )
+ {
+ SwitchHeldItemTo( pItem );
+ m_pHeldItem = pItem;
+ return true;
+ }
+ }
+
+ // If we were trying to switch to a new item, and it's not valid, stick to our current
+ if ( iDesiredSlot != m_iCurrentSlotIndex )
+ {
+ UpdateHeldItem( m_iCurrentSlotIndex );
+ return false;
+ }
+
+ // We were trying to stay on the current weapon, and it's not valid. Find anything.
+ HoldFirstValidItem();
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ClearScene( void )
+{
+ if ( m_pScene )
+ {
+ delete m_pScene;
+ }
+
+ m_pScene = NULL;
+ m_flSceneTime = 0;
+ m_flSceneEndTime = 0;
+ m_flLastTickTime = 0;
+ m_bLoopScene = true;
+ //memset( m_flexWeight, 0, sizeof( m_flexWeight ) );
+}
+
+extern CChoreoStringPool g_ChoreoStringPool;
+CChoreoScene *LoadSceneForModel( const char *filename, IChoreoEventCallback *pCallback, float *flSceneEndTime )
+{
+ char loadfile[ 512 ];
+ V_strcpy_safe( loadfile, filename );
+ V_SetExtension( loadfile, ".vcd", sizeof( loadfile ) );
+ V_FixSlashes( loadfile );
+
+ char *pBuffer = NULL;
+ size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile );
+ if ( bufsize <= 0 )
+ return NULL;
+
+ pBuffer = new char[ bufsize ];
+ if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) )
+ {
+ delete[] pBuffer;
+ return NULL;
+ }
+
+ CChoreoScene *pScene;
+ if ( IsBufferBinaryVCD( pBuffer, bufsize ) )
+ {
+ pScene = new CChoreoScene( pCallback );
+ CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY );
+ if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) )
+ {
+ Warning( "Unable to restore binary scene '%s'\n", loadfile );
+ delete pScene;
+ pScene = NULL;
+ }
+ else
+ {
+ pScene->SetPrintFunc( Scene_Printf );
+ pScene->SetEventCallbackInterface( pCallback );
+ }
+ }
+ else
+ {
+ g_TokenProcessor.SetBuffer( pBuffer );
+ pScene = ChoreoLoadScene( loadfile, pCallback, &g_TokenProcessor, Scene_Printf );
+ }
+
+ delete[] pBuffer;
+
+ if ( flSceneEndTime != NULL )
+ {
+ // find the scene length
+ // The scene is as long as the end point for the last event unless one of the events is a loop
+ *flSceneEndTime = 0.0f;
+ bool bSetEndTime = false;
+ for ( int i = 0; i < pScene->GetNumEvents(); i++ )
+ {
+ CChoreoEvent *pEvent = pScene->GetEvent( i );
+ if ( pEvent->GetType() == CChoreoEvent::LOOP )
+ {
+ *flSceneEndTime = -1.0f;
+ bSetEndTime = false;
+ break;
+ }
+
+ if ( pEvent->GetEndTime() > *flSceneEndTime )
+ {
+ *flSceneEndTime = pEvent->GetEndTime();
+ bSetEndTime = true;
+ }
+ }
+
+ if ( bSetEndTime )
+ {
+ *flSceneEndTime += 0.1f; // give time for lerp to idle pose
+ }
+ }
+
+ return pScene;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::PlayVCD( const char *pszVCD, const char *pszWeaponEntityRequired /*= NULL*/, bool bLoopVCD /*= true*/, bool bFileNameOnly /*= true*/ )
+{
+ m_pszVCD = pszVCD;
+ m_pszWeaponEntityRequired = pszWeaponEntityRequired;
+ m_bLoopVCD = bLoopVCD;
+ m_bVCDFileNameOnly = bFileNameOnly;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::FireEvent( const char *pszEventName, const char *pszEventOptions )
+{
+ //Plat_DebugString( CFmtStr( "********* ANIM EVENT: %s\n", pszEventName ) );
+
+ if ( V_strcmp( pszEventName, "AE_WPN_HIDE" ) == 0 )
+ {
+ int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) );
+ if ( nWeaponIndex >= 0 )
+ {
+ m_aMergeMDLs[nWeaponIndex].m_bDisabled = true;
+ }
+ }
+ else if ( V_strcmp( pszEventName, "AE_WPN_UNHIDE" ) == 0 )
+ {
+ int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) );
+ if ( nWeaponIndex >= 0 )
+ {
+ m_aMergeMDLs[nWeaponIndex].m_bDisabled = false;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SwitchHeldItemTo( CEconItemView *pItem )
+{
+ m_nBody = 0;
+
+ ClearScene();
+
+ // Clear out visible items, and re-equip out wearables
+ RemoveAdditionalModels();
+ EquipAllWearables( pItem );
+
+ // Then equip the held item
+ EquipItem( pItem );
+ m_iCurrentSlotIndex = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ m_pHeldItem = pItem;
+
+ m_StatTrackModel.m_bDisabled = true;
+ m_StatTrackModel.m_MDL.SetMDL( MDLHANDLE_INVALID );
+ CAttribute_String attrModule;
+ static CSchemaAttributeDefHandle pAttr_module( "weapon_uses_stattrak_module" );
+ if ( m_pHeldItem->FindAttribute( pAttr_module, &attrModule ) && attrModule.has_value() )
+ {
+ // Allow for already strange items
+ bool bIsStrange = false;
+ if ( m_pHeldItem->GetQuality() == AE_STRANGE )
+ {
+ bIsStrange = true;
+ }
+
+ if ( !bIsStrange )
+ {
+ // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ if ( m_pHeldItem->FindAttribute( GetKillEaterAttr_Score( i ) ) )
+ {
+ bIsStrange = true;
+ break;
+ }
+ }
+ }
+
+ if ( bIsStrange )
+ {
+ static CSchemaAttributeDefHandle pAttr_moduleScale( "weapon_stattrak_module_scale" );
+ // Does it have a stat track module
+ m_flStatTrackScale = 1.0f;
+ uint32 unFloatAsUint32 = 1;
+ if ( m_pHeldItem->FindAttribute( pAttr_moduleScale, &unFloatAsUint32 ) )
+ {
+ m_flStatTrackScale = (float&)unFloatAsUint32;
+ }
+
+ MDLHandle_t hStatTrackMDL = mdlcache->FindMDL( "models/weapons/c_models/stattrack.mdl" );
+ if ( mdlcache->IsErrorModel( hStatTrackMDL ) )
+ {
+ hStatTrackMDL = MDLHANDLE_INVALID;
+ }
+ m_StatTrackModel.m_MDL.SetMDL( hStatTrackMDL );
+ mdlcache->Release( hStatTrackMDL ); // counterbalance addref from within FindMDL
+
+ m_StatTrackModel.m_MDL.m_pProxyData = static_cast<IClientRenderable*>(pItem);
+ m_StatTrackModel.m_bDisabled = false;
+ m_StatTrackModel.m_MDL.m_nSequence = ACT_IDLE;
+ SetIdentityMatrix( m_StatTrackModel.m_MDLToWorld );
+ }
+ }
+
+ SetSequenceLayers( NULL, 0 );
+
+ // See if our VCD is overridden
+ if ( m_pszVCD )
+ {
+ // Make sure we're holding the weapon, if it's required
+ bool bCanRunScene = true;
+ if ( m_pszWeaponEntityRequired && *m_pszWeaponEntityRequired )
+ {
+ bCanRunScene = false;
+
+ if ( pItem && pItem->IsValid() )
+ {
+ const char *pszClassName = pItem->GetStaticData()->GetItemClass();
+ if ( pszClassName && *pszClassName )
+ {
+ bCanRunScene = V_stricmp( pszClassName, m_pszWeaponEntityRequired ) == 0;
+ }
+ }
+ }
+
+ if ( bCanRunScene )
+ {
+ if ( m_bVCDFileNameOnly )
+ {
+ // auto complete relative path for the vcd file
+ V_sprintf_safe( g_szSceneTmpName, "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[m_iCurrentClassIndex], m_pszVCD );
+ }
+ else
+ {
+ // m_pszVCD should be a valid relative path
+ V_strcpy_safe( g_szSceneTmpName, m_pszVCD );
+ }
+
+ m_pScene = LoadSceneForModel( g_szSceneTmpName, this, &m_flSceneEndTime );
+ m_bLoopScene = m_bLoopVCD;
+
+ return;
+ }
+ }
+
+ const char *pScene = NULL;
+ const char *pSequence = NULL;
+ const char *pRequiredItem = NULL;
+ bool bRemoveTauntParticles = true;
+
+ if ( IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex, &pSequence, &pRequiredItem, &pScene ) )
+ {
+ MDLCACHE_CRITICAL_SECTION();
+
+ if ( pScene )
+ {
+ m_pScene = LoadSceneForModel( pScene, this, &m_flSceneEndTime );
+
+ // load custom prop for taunt
+ const char *pszProp = pItem->GetStaticData()->GetTauntData()->GetProp( m_iCurrentClassIndex );
+ if ( pszProp )
+ {
+ LoadAndAttachAdditionalModel( pszProp, pItem );
+ }
+
+ // force taunt to equip certain slot
+ static CSchemaAttributeDefHandle pAttrDef_TauntForceWeaponSlot( "taunt force weapon slot" );
+ const char* pszTauntForceWeaponSlotName = NULL;
+ if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( pItem, pAttrDef_TauntForceWeaponSlot, &pszTauntForceWeaponSlotName ) )
+ {
+ int iForceWeaponSlot = StringFieldToInt( pszTauntForceWeaponSlotName, GetItemSchema()->GetWeaponTypeSubstrings() );
+ EquipRequiredLoadoutSlot( iForceWeaponSlot );
+ }
+ }
+ else
+ {
+ ClearScene();
+
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ int iSequence = LookupSequence( &studioHdr, pSequence );
+ if ( iSequence >= 0 )
+ {
+ // does a weapon need to be equipped?
+ loadout_positions_t requiredLoadoutItem = LOADOUT_POSITION_INVALID;
+ if ( pRequiredItem )
+ {
+ requiredLoadoutItem = (loadout_positions_t)StringFieldToInt( pRequiredItem, ItemSystem()->GetItemSchema()->GetLoadoutStrings( pItem->GetItemDefinition()->GetEquipType() ) );
+ }
+
+ EquipRequiredLoadoutSlot( requiredLoadoutItem );
+
+ // finally, set the sequence layers
+ MDLSquenceLayer_t tmpSequenceLayers[1];
+ tmpSequenceLayers[0].m_nSequenceIndex = iSequence;
+ tmpSequenceLayers[0].m_flWeight = 1.0;
+ tmpSequenceLayers[0].m_bNoLoop = false;
+ tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime;
+ SetSequenceLayers( tmpSequenceLayers, 1 );
+ }
+ }
+
+ // Taunt Particles
+ static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" );
+ uint32 unUnusualEffectIndex = 0;
+ if ( pItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 )
+ {
+ const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex );
+ if ( pParticleSystem )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_TAUNT ] );
+ m_aParticleSystems[ SYSTEM_TAUNT ] = CreateParticleData( pParticleSystem->pszSystemName );
+ m_flTauntParticleRefireTime = gpGlobals->curtime + pParticleSystem->fRefireTime;
+ m_flTauntParticleRefireRate = pParticleSystem->fRefireTime;
+ m_bDrawTauntParticles = true;
+ bRemoveTauntParticles = false;
+ }
+ }
+ }
+
+ // Clear out taunt particles
+ if ( bRemoveTauntParticles && m_aParticleSystems[SYSTEM_TAUNT] )
+ {
+ m_bDrawTauntParticles = false;
+ SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] );
+ }
+
+ // Check if it has a PoseParameter Attributes (r_hand_grip)
+ float flPose = 0;
+ static CSchemaAttributeDefHandle pAttrDef_RightHandPose( "righthand pose parameter" );
+ uint32 iValue = 0;
+ if ( pItem->FindAttribute( pAttrDef_RightHandPose, &iValue ) )
+ {
+ flPose = (float&)iValue;
+ }
+ SetPoseParameterByName( "r_hand_grip", flPose );
+
+ // Check for hand particles (spell book)
+ // always nuke
+ if ( m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_ACTIONSLOT ] );
+ }
+ m_bDrawActionSlotEffects = false;
+ if ( pItem->GetStaticData()->GetItemClass() )
+ {
+ m_bDrawActionSlotEffects = FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_spellbook" );
+ }
+
+ // update eyeglows
+ m_bUpdateEyeGlows = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EquipRequiredLoadoutSlot( int iRequiredLoadoutSlot )
+{
+ if ( iRequiredLoadoutSlot != LOADOUT_POSITION_INVALID )
+ {
+ int iDesiredSlot = -1;
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ if ( pItem->GetStaticData()->IsAWearable() )
+ continue;
+ if ( pItem->GetAnimationSlot() == -2 )
+ continue;
+
+ // Found a weapon. Wield it.
+ if ( iRequiredLoadoutSlot == pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) )
+ {
+ iDesiredSlot = i;
+ break;
+ }
+ }
+
+ if ( iDesiredSlot >= 0 )
+ {
+ EquipItem( m_ItemsToCarry[iDesiredSlot] );
+ }
+ else
+ {
+ // If we didn't find a weapon in the appropriate slot, get the base item
+ CEconItemView *pWeapon = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, iRequiredLoadoutSlot );
+ if ( pWeapon && pWeapon->IsValid() )
+ {
+ EquipItem( pWeapon );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateWeaponBodygroups( bool bModifyDeployedOnlyBodygroups )
+{
+ for ( int i=0; i<MAX_WEAPON_SLOTS; i++ )
+ {
+ CEconItemView *pItem = GetItemInSlot( i );
+ if ( !pItem )
+ continue;
+
+ if ( pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly() != bModifyDeployedOnlyBodygroups )
+ continue;
+
+ if ( !(m_pHeldItem == pItem || !pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly()) )
+ continue;
+
+ UpdateHiddenBodyGroups( pItem );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateHiddenBodyGroups( CEconItemView* pItem )
+{
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ int iNumBodyGroups = pItem->GetStaticData()->GetNumModifiedBodyGroups( 0 );
+ for ( int i=0; i<iNumBodyGroups; ++i )
+ {
+ int iState = 0;
+ const char *pszBodyGroup = pItem->GetStaticData()->GetModifiedBodyGroup( 0, i, iState );
+ int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodyGroup );
+
+ if ( iBodyGroup == -1 )
+ continue;
+
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState );
+ SetBody( m_nBody );
+ }
+
+ // Handle style-based bodygroups
+ const CEconItemDefinition *pItemDef = pItem->GetItemDefinition();
+ const CEconStyleInfo *pStyle = pItemDef ? pItemDef->GetStyleInfo( pItem->GetStyle() ) : NULL;
+ if ( pStyle )
+ {
+ FOR_EACH_VEC( pStyle->GetAdditionalHideBodygroups(), i )
+ {
+ int iBodyGroup = FindBodygroupByName( &studioHdr, pStyle->GetAdditionalHideBodygroups()[i] );
+
+ if ( iBodyGroup == -1 )
+ continue;
+
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, 1 ); // force state to '1' here to mean hidden
+ SetBody( m_nBody );
+ }
+ }
+
+ // Handle world model bodygroup overrides
+ int iBodyOverride = pItem->GetStaticData()->GetWorldmodelBodygroupOverride( m_iTeam );
+ int iBodyStateOverride = pItem->GetStaticData()->GetWorldmodelBodygroupStateOverride( m_iTeam );
+ if ( iBodyOverride > -1 && iBodyStateOverride > -1 )
+ {
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyOverride, iBodyStateOverride );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconItemView *CTFPlayerModelPanel::GetItemInSlot( int iSlot )
+{
+ CEconItemView *pOwnedItemInSlot = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, iSlot );
+
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ if ( iSlot == iLoadoutSlot )
+ return pItem;
+
+ // GetLoadoutSlot will not work for misc2, taunt2-8 because it will always return misc/taunt
+ if ( pOwnedItemInSlot && pOwnedItemInSlot->GetItemDefIndex() == pItem->GetItemDefIndex() )
+ return pItem;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EquipAllWearables( CEconItemView *pHeldItem )
+{
+ // First, reset all our bodygroups
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ const CEconItemSchema::BodygroupStateMap_t& mapBodygroupState = GetItemSchema()->GetDefaultBodygroupStateMap();
+
+ FOR_EACH_MAP_FAST( mapBodygroupState, i )
+ {
+ const char *pszBodygroupName = mapBodygroupState.Key(i);
+ int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodygroupName );
+ if ( iBodyGroup > -1 )
+ {
+ int iState = mapBodygroupState[i];
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState );
+ }
+ }
+
+ SetBody( m_nBody );
+
+ UpdateWeaponBodygroups( false );
+
+ // Now equip each of our wearables
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ // If it's a wearable item, we put it on.
+ if ( pItem->GetStaticData()->IsAWearable() )
+ {
+ EquipItem( pItem );
+ }
+
+ // Then see if there's an extra wearable we need to attach for this item
+ const char *pszAttached = pItem->GetExtraWearableModel();
+ if ( pszAttached && pszAttached[ 0 ] )
+ {
+ const char *pszViewModelAttached = pItem->GetExtraWearableViewModel();
+ if ( pHeldItem == pItem || pszViewModelAttached == NULL || pszViewModelAttached[ 0 ] == '\0' || pszViewModelAttached[ 0 ] == '?' )
+ {
+ LoadAndAttachAdditionalModel( pszAttached, pItem );
+ }
+ }
+ }
+
+ UpdateWeaponBodygroups( true );
+
+ SetBody( m_nBody );
+
+ UpdatePreviewVisuals();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EquipItem( CEconItemView *pItem )
+{
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return;
+
+ const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition();
+ Assert( pItemDef );
+
+ // Change team number so skins composite correctly
+ pItem->SetTeamNumber( m_iTeam );
+
+ // Non wearables can modify the animation
+ if ( !pItemDef->IsAWearable() )
+ {
+ int iAnimSlot = pItem->GetAnimationSlot();
+
+ // Ignore items that don't want to control player animation
+ if ( iAnimSlot == -2 )
+ return;
+
+ if ( iAnimSlot == -1 )
+ {
+ iAnimSlot = pItemDef->GetLoadoutSlot( m_iCurrentClassIndex );
+ }
+
+ const CUtlVector<const char *>& vecWeaponTypeSubstrings = GetItemSchema()->GetWeaponTypeSubstrings();
+ if ( vecWeaponTypeSubstrings.IsValidIndex( iAnimSlot ) )
+ {
+ int iAnim = FindAnimByName( vecWeaponTypeSubstrings[iAnimSlot] );
+ SetModelAnim( iAnim );
+ }
+ }
+
+ // Attach the models for the item
+ const char *pszAttached = pItem->GetWorldDisplayModel();
+ if ( !pszAttached )
+ {
+ pszAttached = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam );
+ }
+
+ if ( pszAttached && pszAttached[0] )
+ {
+ LoadAndAttachAdditionalModel( pszAttached, pItem );
+
+ int iTeam = pItemDef->GetBestVisualTeamData( m_iTeam );
+ // Set attached models if viewable third-person.
+ {
+ const int iNumAttachedModels = pItemDef->GetNumAttachedModels( iTeam );
+ for ( int i = 0; i < iNumAttachedModels; ++i )
+ {
+ attachedmodel_t *pModel = pItemDef->GetAttachedModelData( iTeam, i );
+
+ if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) )
+ continue;
+
+ if ( !pModel->m_pszModelName )
+ {
+ Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i );
+ continue;
+ }
+
+ LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem );
+ }
+ }
+
+ // Festive
+ // Set attached models if viewable third-person.
+ static CSchemaAttributeDefHandle pAttr_is_festivized( "is_festivized" );
+ if ( pAttr_is_festivized && pItem->FindAttribute( pAttr_is_festivized ) )
+ {
+ const int iNumAttachedModels = pItemDef->GetNumAttachedModelsFestivized( iTeam );
+ for ( int i = 0; i < iNumAttachedModels; ++i )
+ {
+ attachedmodel_t *pModel = pItemDef->GetAttachedModelDataFestivized( iTeam, i );
+
+ if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) )
+ continue;
+
+ if ( !pModel->m_pszModelName )
+ {
+ Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i );
+ continue;
+ }
+
+ LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem );
+ }
+ }
+ }
+
+ // Hide any item associated groups.
+ UpdateHiddenBodyGroups( pItem );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerModelPanel::AddCarriedItem( CEconItemView *pItem )
+{
+ CEconItemView *pNewItem = new CEconItemView;
+ *pNewItem = *pItem;
+ int iIdx = m_ItemsToCarry.AddToTail( pNewItem );
+
+ // This is a terrible hack. If we have team paint, we need an entity to find out what team
+ // we're on, but in this panel we don't have one. Instead, we force a flag all the way through
+ // the system on the CEconItemView so that the low-level paint code can pull from it if necessary.
+ if ( GetTeam() == TF_TEAM_BLUE )
+ {
+ pNewItem->SetClientItemFlags( kEconItemFlagClient_ForceBlueTeam );
+ }
+
+ return iIdx;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ClearCarriedItems( void )
+{
+ RemoveAdditionalModels();
+ m_ItemsToCarry.PurgeAndDeleteElements();
+ m_pHeldItem = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::RemoveAdditionalModels( void )
+{
+ ClearMergeMDLs();
+
+ // Unregister for all callbacks
+ modelinfo->UnregisterModelLoadCallback( -1, this );
+ m_vecDynamicAssetsLoaded.Purge();
+ m_vecItemsLoaded.PurgeAndDeleteElements();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::LoadAndAttachAdditionalModel( const char *pMDLName, CEconItemView *pItem )
+{
+ int nModelIndex = -1;
+
+ if ( pItem->GetStaticData()->IsContentStreamable() )
+ {
+ // Get the client-only dynamic model index. The auto-addref
+ // of vecDynamicAssetsLoaded will actually trigger the load.
+ nModelIndex = modelinfo->RegisterDynamicModel( pMDLName, true );
+ // Dynamic models never fail to register in this engine.
+ Assert( nModelIndex != -1 );
+ }
+ else
+ {
+ // Is the (non-streamable) model already precached? If so, use it.
+ nModelIndex = modelinfo->GetModelIndex( pMDLName );
+ }
+
+ if ( nModelIndex == -1 )
+ {
+ MDLHandle_t hMDL = vgui::MDLCache()->FindMDL( pMDLName );
+ Assert( hMDL != MDLHANDLE_INVALID );
+ if ( hMDL != MDLHANDLE_INVALID )
+ {
+ // Model not loaded, not dynamic. Hard load and exit out.
+ SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem), pItem->GetSkin( m_iTeam ) );
+ }
+ m_MergeMDL = hMDL;
+ return;
+ }
+
+ CEconItemView *pClone = new CEconItemView;
+ *pClone = *pItem;
+ m_vecDynamicAssetsLoaded[ m_vecDynamicAssetsLoaded.AddToTail() ] = nModelIndex;
+ m_vecItemsLoaded.AddToTail( pClone );
+
+ // callback triggers immediately if not dynamic
+ modelinfo->RegisterModelLoadCallback( nModelIndex, this, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void SetMDLSkinForTeam( CMDL *pMDL, const CEconItemView *pItem, int iTeam )
+{
+ Assert( pItem );
+
+ if ( !pMDL )
+ return;
+
+ // Ask the item for a skin...
+ int nSkin = pItem->GetSkin( iTeam );
+
+ if ( nSkin == -1 )
+ {
+ // ... if not, use the team skin.
+ nSkin = iTeam == TF_TEAM_RED ? 0 : 1;
+ }
+
+ pMDL->m_nSkin = nSkin;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::OnModelLoadComplete( const model_t *pModel )
+{
+ CEconItemView *pItem = NULL;
+ FOR_EACH_VEC_BACK( m_vecDynamicAssetsLoaded, i )
+ {
+ if ( modelinfo->GetModel( m_vecDynamicAssetsLoaded[ i ] ) == pModel )
+ {
+ pItem = GetPreviewItem( m_vecItemsLoaded[ i ] );
+ break;
+ }
+ }
+
+ Assert( pItem );
+ if ( pItem )
+ {
+ MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel );
+ Assert( hMDL != MDLHANDLE_INVALID );
+ if ( hMDL != MDLHANDLE_INVALID )
+ {
+ SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem) );
+
+ int nBody = 0;
+ if ( pItem->GetStaticData()->UsesPerClassBodygroups( m_iTeam ) )
+ {
+ CMDL *pMDL = GetMergeMDL(hMDL);
+ if ( pMDL )
+ {
+ // Classes start at 1, bodygroups at 0, so we shift them all back 1.
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr sHDR( pMDL->GetStudioHdr(), g_pMDLCache );
+ ::SetBodygroup( &sHDR, nBody, 1, m_iCurrentClassIndex-1 );
+ pMDL->m_nBody = nBody;
+ }
+ }
+
+ // Set the custom skin.
+ SetMDLSkinForTeam( GetMergeMDL( hMDL ), pItem, m_iTeam );
+ }
+ }
+}
+
+void CTFPlayerModelPanel::SetTeam( int iTeam )
+{
+ m_iTeam = iTeam;
+
+ UpdatePreviewVisuals();
+}
+
+void CTFPlayerModelPanel::UpdatePreviewVisuals()
+{
+ // Assume skin will be chosen based only on the preview team
+ int iSkin = m_iTeam == TF_TEAM_RED ? 0 : 1;
+
+ // Check if any of the items we're carrying should override this
+ static CSchemaAttributeDefHandle pAttrDef_PlayerSkinOverride( "player skin override" );
+ Assert( pAttrDef_PlayerSkinOverride );
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ if ( !pItem )
+ continue;
+ float fSkinOverride = 0.0f;
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrDef_PlayerSkinOverride, &fSkinOverride ) && fSkinOverride == 1.0f )
+ {
+ C_TFPlayer::AdjustSkinIndexForZombie( m_iCurrentClassIndex, iSkin );
+ break;
+ }
+ Assert( fSkinOverride == 0.0f );
+ }
+
+ // Set the player model skin.
+ SetSkin( iSkin );
+
+ // Set the weapon's skin.
+ if ( m_MergeMDL && m_pHeldItem )
+ {
+ SetMDLSkinForTeam( GetMergeMDL( m_MergeMDL ), GetPreviewItem( m_pHeldItem ), m_iTeam );
+ }
+
+ // Set the skin for all other equipped items (wearables, etc).
+ for ( int i=0; i<m_vecDynamicAssetsLoaded.Count(); i++ )
+ {
+ const model_t *pModel = modelinfo->GetModel( m_vecDynamicAssetsLoaded[i] );
+ if ( pModel )
+ {
+ MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel );
+
+ // We're iterating over a list of the dynamic assets that we've completed streaming in, but
+ // we want to set the style based on the "preview item" definition if possible.
+ SetMDLSkinForTeam( GetMergeMDL( hMDL ), GetPreviewItem( m_vecItemsLoaded[i] ), m_iTeam );
+ }
+ }
+}
+
+CEconItemView *CTFPlayerModelPanel::GetPreviewItem( CEconItemView *pMatchItem )
+{
+ Assert( pMatchItem );
+ if ( !pMatchItem )
+ return NULL;
+
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ if ( *pMatchItem == *pItem )
+ return pItem;
+ }
+
+ return pMatchItem;
+}
+
+int ClassZoomZ[] =
+{
+ 0,
+ 20, // TF_CLASS_SCOUT,
+ 25, // TF_CLASS_SNIPER,
+ 20, // TF_CLASS_SOLDIER,
+ 22, // TF_CLASS_DEMOMAN,
+ 30, // TF_CLASS_MEDIC,
+ 30, // TF_CLASS_HEAVYWEAPONS,
+ 22, // TF_CLASS_PYRO,
+ 27, // TF_CLASS_SPY,
+ 20, // TF_CLASS_ENGINEER,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ToggleZoom()
+{
+ m_bZoomedToHead = !m_bZoomedToHead;
+
+ // NOTE: GetZoomOffset() relies on m_bZoomedToHead being up to date
+ m_vecPlayerPos += GetZoomOffset();
+
+ SetModelAnglesAndPosition( m_angPlayer, m_vecPlayerPos );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CTFPlayerModelPanel::GetZoomOffset()
+{
+ const Vector vecOffset( 100, 0, ClassZoomZ[m_iCurrentClassIndex] );
+ return m_bZoomedToHead ? -vecOffset : vecOffset;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::PrePaint3D( IMatRenderContext *pRenderContext )
+{
+ if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
+ {
+ modelrender->ForcedMaterialOverride( *g_PlayerPreviewEffect.GetInvulnMaterialRef() );
+ }
+
+ BaseClass::PrePaint3D( pRenderContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::PostPaint3D( IMatRenderContext *pRenderContext )
+{
+ if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
+ {
+ modelrender->ForcedMaterialOverride( NULL );
+ }
+
+ static bool bAlternate = false;
+ Vector vColor = bAlternate ? m_vEyeGlowColor1 : m_vEyeGlowColor2;
+ bAlternate = !bAlternate;
+ // Eye glows
+ if ( m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ] )
+ {
+ m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+ if ( m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ] )
+ {
+ m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+
+ if ( m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ] )
+ {
+ m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+ if ( m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ])
+ {
+ m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+
+ m_bUpdateEyeGlows = false;
+ m_bPlaySparks = false;
+
+ // remove all particles that are not up-to-date before simulating the updated ones in the base
+ for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
+ {
+ if ( m_aParticleSystems[i] && !m_aParticleSystems[i]->m_bIsUpdateToDate )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[i] );
+ }
+ }
+
+ BaseClass::PostPaint3D( pRenderContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose : Called by base Mdlpanel when a merged mdl has been drawn
+// For TF we use this as a way to render effects on top of model as appropriate (ie Unusual effects)
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix )
+{
+ if ( !m_bUseParticle )
+ return;
+
+ // Eye Glows
+ UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, true );
+ UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, false );
+
+ // Right hand
+ UpdateActionSlotEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix );
+
+ // Taunt Effects
+ UpdateTauntEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix );
+}
+
+CEconItemView *CTFPlayerModelPanel::GetLoadoutItemFromMDLHandle( loadout_positions_t iPosition, MDLHandle_t mdlHandle )
+{
+ // Check if we have a particle hat, if not ignore
+ CEconItemView *pEconItem = NULL;
+
+ // Find this item
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ if ( ( IsMiscSlot( iLoadoutSlot ) && IsMiscSlot( iPosition ) ) ||
+ ( IsValidPickupWeaponSlot( iLoadoutSlot ) && iLoadoutSlot == iPosition ) )
+ {
+ const char * pDisplayModel = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam );
+ if ( pDisplayModel )
+ {
+ MDLHandle_t hMDLFindResult = vgui::MDLCache()->FindMDL( pDisplayModel );
+ // compare the model to make sure that this is the same item
+ if ( hMDLFindResult == mdlHandle )
+ {
+ pEconItem = pItem;
+ vgui::MDLCache()->Release(hMDLFindResult); // counterbalance addref from within FindMDL
+ break;
+ }
+ vgui::MDLCache()->Release(hMDLFindResult);
+ }
+ }
+ }
+
+ return pEconItem;
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::RenderingMergedModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix )
+{
+ if ( !m_bUseParticle )
+ return;
+
+ static struct MergeModelSlot_t
+ {
+ loadout_positions_t iPosition;
+ modelpanel_particle_system_t iSystem;
+ } s_mergeModelSlot[] =
+ {
+ { LOADOUT_POSITION_HEAD, SYSTEM_HEAD },
+ { LOADOUT_POSITION_MISC, SYSTEM_MISC1 },
+ { LOADOUT_POSITION_MISC2, SYSTEM_MISC2 },
+ { LOADOUT_POSITION_PRIMARY, SYSTEM_WEAPON },
+ { LOADOUT_POSITION_SECONDARY, SYSTEM_WEAPON },
+ { LOADOUT_POSITION_MELEE, SYSTEM_WEAPON },
+ };
+
+ modelpanel_particle_system_t iSystem = SYSTEM_HEAD;
+ loadout_positions_t iPosition = LOADOUT_POSITION_INVALID;
+ CEconItemView *pEconItem = NULL;
+ int count = ARRAYSIZE( s_mergeModelSlot );
+ for ( int i=0; i<count; ++i )
+ {
+ // find the item for this model
+ pEconItem = GetLoadoutItemFromMDLHandle( s_mergeModelSlot[i].iPosition, mdlHandle );
+ if ( pEconItem )
+ {
+ iPosition = s_mergeModelSlot[i].iPosition;
+ iSystem = s_mergeModelSlot[i].iSystem;
+
+ // this is horrible but this fixes multiple unusual cosmetics with same default loadout to update their particles
+ if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate )
+ continue;
+
+ break;
+ }
+ }
+
+ // couldn't find matching item for this model, do nothing
+ if ( !pEconItem )
+ return;
+
+ // Unusual Particles
+ // Update Misc Particles 1 by 1, Unfortunately the equip location is generic (MISC_SLOT) and not the specific slot
+ // so we have to test each slot individually
+ UpdateCosmeticParticles( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, iSystem, pEconItem );
+
+ if ( m_iCurrentSlotIndex == iPosition )
+ {
+ RenderStatTrack( pStudioHdr, pWorldMatrix );
+ }
+}
+
+IMaterial* CTFPlayerModelPanel::GetOverrideMaterial( MDLHandle_t mdlHandle )
+{
+ loadout_positions_t s_iPosition[] = {
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_MISC2,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE
+ };
+
+ int count = ARRAYSIZE( s_iPosition );
+ for ( int i = 0; i < count; ++i )
+ {
+ CEconItemView *pEconItem = GetLoadoutItemFromMDLHandle( s_iPosition[ i ], mdlHandle );
+ if ( pEconItem )
+ return pEconItem->GetMaterialOverride( m_iTeam );
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix )
+{
+ // Draw the merge MDLs.
+ if ( !m_StatTrackModel.m_bDisabled )
+ {
+ matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES];
+
+ // Get the merge studio header.
+ studiohdr_t *pStatTrackStudioHdr = m_StatTrackModel.m_MDL.GetStudioHdr();
+ matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0];
+
+ // If we have a valid mesh, bonemerge it. If we have an invalid mesh we can't bonemerge because
+ // it'll crash trying to pull data from the missing header.
+ if ( pStatTrackStudioHdr != NULL )
+ {
+ CStudioHdr mergeHdr( pStatTrackStudioHdr, g_pMDLCache );
+ m_StatTrackModel.m_MDL.SetupBonesWithBoneMerge( &mergeHdr, pMergeBoneToWorld, pStudioHdr, pWorldMatrix, m_StatTrackModel.m_MDLToWorld );
+ for ( int i=0; i<mergeHdr.numbones(); ++i )
+ {
+ MatrixScaleBy( m_flStatTrackScale, pMergeBoneToWorld[i] );
+ }
+ m_StatTrackModel.m_MDL.Draw( m_StatTrackModel.m_MDLToWorld, pMergeBoneToWorld );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::UpdateCosmeticParticles(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix,
+ modelpanel_particle_system_t iSystem,
+ CEconItemView *pEconItem
+)
+{
+ if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate )
+ return false;
+
+ attachedparticlesystem_t *pParticleSystem = NULL;
+
+ // do community_sparkle effect if this is a community item?
+ const int iQualityParticleType = pEconItem->GetQualityParticleType();
+ if ( iQualityParticleType > 0 )
+ {
+ pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType );
+ }
+
+ if ( !pParticleSystem )
+ {
+ // does this hat even have a particle effect
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ uint32 iValue = 0;
+ if ( !pEconItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) )
+ {
+ return false;
+ }
+
+ const float& value_as_float = (float&)iValue;
+ pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( value_as_float );
+ }
+
+ // failed to find any particle effect
+ if ( !pParticleSystem )
+ {
+ return false;
+ }
+
+ // Team Color
+ if ( GetTeam() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ))
+ {
+ static char pBlue[256];
+ V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
+ pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
+ if ( !pParticleSystem )
+ {
+ return false;
+ }
+ }
+
+ // if this thing has a bip_head or prp_helmet (aka a hat)
+ int iBone = Studio_BoneIndexByName( pStudioHdr, "bip_head" );
+ if ( iBone < 0 )
+ {
+ iBone = Studio_BoneIndexByName( pStudioHdr, "prp_helmet" );
+ if ( iBone < 0 )
+ {
+ iBone = Studio_BoneIndexByName( pStudioHdr, "prp_hat" );
+ }
+ }
+
+ // default to root
+ if ( iBone < 0 )
+ {
+ iBone = 0;
+ }
+
+ // Get Use Head Origin
+ CUtlVector< int > vecAttachments;
+ static CSchemaAttributeDefHandle pAttrDef_UseHead( "particle effect use head origin" );
+ uint32 iUseHead = 0;
+ if ( !pEconItem->FindAttribute( pAttrDef_UseHead, &iUseHead ) || !iUseHead == 0 )
+ {
+ // not using head? try searching for attachment points
+ for ( int i=0; i<ARRAYSIZE( pParticleSystem->pszControlPoints ); ++i )
+ {
+ const char *pszAttachmentName = pParticleSystem->pszControlPoints[i];
+ if ( pszAttachmentName && pszAttachmentName[0] )
+ {
+ int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttachmentName );
+ if ( iAttachment < 0 )
+ continue;
+
+ vecAttachments.AddToTail( iAttachment );
+ }
+ }
+ }
+
+ static char pszFullname[256];
+ const char* pszSystemName = pParticleSystem->pszSystemName;
+ // Weapon Remap for a Base Effect to be used on a specific weapon
+ if ( pParticleSystem->bUseSuffixName && pEconItem && pEconItem->GetItemDefinition()->GetParticleSuffix() )
+ {
+ V_strcpy_safe( pszFullname, pParticleSystem->pszSystemName );
+ V_strcat_safe( pszFullname, "_" );
+ V_strcat_safe( pszFullname, pEconItem->GetItemDefinition()->GetParticleSuffix() );
+ pszSystemName = pszFullname;
+ }
+
+ // Update the Particles and render them
+ if ( m_aParticleSystems[ iSystem ] )
+ {
+ // Check if its a new particle system
+ if ( V_strcmp( m_aParticleSystems[ iSystem ]->m_pParticleSystem->GetName(), pszSystemName ) )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ iSystem ] );
+ m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName );
+ }
+ }
+ else
+ {
+ // create
+ m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName );
+ }
+
+ // Particle system does not exist
+ if ( !m_aParticleSystems[ iSystem ] )
+ return false;
+
+ // Get offset if it exists (and if we're using head offset)
+ static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
+ uint32 iOffset = 0;
+ Vector vecParticleOffset( 0, 0, 0 );
+ if ( iUseHead > 0 && pEconItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) )
+ {
+ vecParticleOffset.z = (float&)iOffset;
+ }
+
+ m_aParticleSystems[ iSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, iBone, vecParticleOffset );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateEyeGlows(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix,
+ bool bIsRightEye
+) {
+ float flOffset = 0;
+ modelpanel_particle_system_t eyeSystem = bIsRightEye ? SYSTEM_EYEGLOW_RIGHT : SYSTEM_EYEGLOW_LEFT;
+ modelpanel_particle_system_t sparkSystem = bIsRightEye ? SYSTEM_EYESPARK_RIGHT : SYSTEM_EYESPARK_LEFT;
+ const char* pszAttach = bIsRightEye ? "eyeglow_R" : "eyeglow_L";
+
+ // is this a model we care about?
+ int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttach );
+ if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 )
+ return;
+
+ if ( m_bUpdateEyeGlows )
+ {
+ const char* pszGlowEffectName = m_pszEyeGlowParticleName;
+
+ // kill old effects
+ SafeDeleteParticleData( &m_aParticleSystems[ eyeSystem ] );
+
+ if ( !bIsRightEye && GetPlayerClass() == TF_CLASS_DEMOMAN )
+ {
+ // demo man has a green eyeglow for eyelander if applicable
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pPlayer )
+ {
+ int iDecaps = pPlayer->m_Shared.GetDecapitations();
+ pszGlowEffectName = pPlayer->GetDemomanEyeEffectName( iDecaps );
+ }
+ }
+
+ if ( pszGlowEffectName && pszGlowEffectName[0] != '\0' )
+ {
+ m_aParticleSystems[ eyeSystem ] = CreateParticleData( pszGlowEffectName );
+ }
+ }
+
+ if ( m_bPlaySparks && !m_vEyeGlowColor1.IsZero() )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ sparkSystem ] );
+
+ // Generate an eye spark as well not for demo
+ m_aParticleSystems[ sparkSystem ] = CreateParticleData( "killstreak_t0_lvl1_flash" );
+ }
+
+ // Tick Update on position
+ if ( m_aParticleSystems[ eyeSystem ] || m_aParticleSystems[ sparkSystem ] )
+ {
+ // Figure out where our attach point is
+ matrix3x4_t matAttachToWorld;
+
+ CUtlVector< int > vecAttachments;
+ vecAttachments.AddToTail( iAttachment );
+
+ // Update control points which is updating the position of the particles
+ Vector vecForward;
+ MatrixGetColumn( matAttachToWorld, 0, vecForward );
+
+ Vector vecParticleOffset = vecForward * flOffset;
+ if ( m_aParticleSystems[ eyeSystem ] )
+ {
+ m_aParticleSystems[ eyeSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset );
+ }
+
+ if ( m_aParticleSystems[ sparkSystem ] )
+ {
+ m_aParticleSystems[ sparkSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateActionSlotEffects(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix
+) {
+ // is this a model we care about?
+ int iAttachment = Studio_FindAttachment( pStudioHdr, "effect_hand_R" );
+ if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 )
+ return;
+
+ if ( !m_bDrawActionSlotEffects )
+ return;
+
+ if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
+ {
+ m_aParticleSystems[ SYSTEM_ACTIONSLOT ] = CreateParticleData( CTFSpellBook::GetHandEffect( m_pHeldItem, 0 ) );
+ }
+
+ if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
+ return;
+
+ CUtlVector< int > vecAttachments;
+ vecAttachments.AddToTail( iAttachment );
+
+ m_aParticleSystems[ SYSTEM_ACTIONSLOT ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments );
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateTauntEffects(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix
+ ) {
+ if ( !m_bDrawTauntParticles )
+ return;
+
+ if ( !m_aParticleSystems[SYSTEM_TAUNT] )
+ return;
+
+ // Check if refire is needed
+ if ( m_flTauntParticleRefireRate > 0 && m_flTauntParticleRefireTime < gpGlobals->curtime )
+ {
+ m_flTauntParticleRefireTime = gpGlobals->curtime + m_flTauntParticleRefireRate;
+
+ // safe off current particle name
+ CUtlString strParticleName = m_aParticleSystems[SYSTEM_TAUNT]->m_pParticleSystem->GetName();
+
+ // remove old particle
+ SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] );
+
+ // create new particle
+ m_aParticleSystems[SYSTEM_TAUNT] = CreateParticleData( strParticleName.String() );
+ }
+
+ matrix3x4_t matAttachToWorld;
+ SetIdentityMatrix( matAttachToWorld );
+
+ CUtlVector< int > vecAttachments;
+ m_aParticleSystems[SYSTEM_TAUNT]->UpdateControlPoints( pStudioHdr, &matAttachToWorld, vecAttachments, 0, m_vecPlayerPos );
+}
+
+//-----------------------------------------------------------------------------
+// Called Externally
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetEyeGlowEffect( const char *pEffectName, Vector vColor1, Vector vColor2, bool bForceUpdate, bool bPlaySparks )
+{
+ m_vEyeGlowColor1 = vColor1;
+ m_vEyeGlowColor2 = vColor2;
+ m_bPlaySparks = bPlaySparks;
+
+ if ( bForceUpdate )
+ {
+ m_bUpdateEyeGlows = true;
+ }
+
+ if ( !pEffectName )
+ {
+ if ( m_pszEyeGlowParticleName[0] != '\0' )
+ {
+ m_bUpdateEyeGlows = true;
+ }
+ m_pszEyeGlowParticleName[0] = '\0';
+ }
+ else if ( !FStrEq( m_pszEyeGlowParticleName, pEffectName) )
+ {
+ V_strcpy_safe( m_pszEyeGlowParticleName, pEffectName );
+ m_bUpdateEyeGlows = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: clear all particles
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::InvalidateParticleEffects()
+{
+ for ( int i=0; i<ARRAYSIZE(m_aParticleSystems); ++i )
+ {
+ if ( m_aParticleSystems[i] )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[i] );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ if ( !event || !event->GetActive() )
+ return;
+
+ CChoreoActor *actor = event->GetActor();
+ if ( actor && !actor->GetActive() )
+ return;
+
+ CChoreoChannel *channel = event->GetChannel();
+ if ( channel && !channel->GetActive() )
+ return;
+
+ //Msg( "Got STARTEVENT at %.2f\n", currenttime );
+ //Msg( "%8.4f: start %s\n", currenttime, event->GetDescription() );
+
+ switch ( event->GetType() )
+ {
+ case CChoreoEvent::SEQUENCE:
+ ProcessSequence( scene, event );
+ break;
+
+ case CChoreoEvent::SPEAK:
+ {
+ if ( m_bDisableSpeakEvent )
+ return;
+
+ // FIXME: dB hack. soundlevel needs to be moved into inside of wav?
+ soundlevel_t iSoundlevel = SNDLVL_TALKING;
+ if ( event->GetParameters2() )
+ {
+ iSoundlevel = (soundlevel_t)atoi( event->GetParameters2() );
+ if ( iSoundlevel == SNDLVL_NONE )
+ {
+ iSoundlevel = SNDLVL_TALKING;
+ }
+ }
+
+ float time_in_past = currenttime - event->GetStartTime() ;
+ float soundtime = gpGlobals->curtime - time_in_past;
+
+ EmitSound_t es;
+ es.m_nChannel = CHAN_VOICE;
+ es.m_flVolume = 1;
+ es.m_SoundLevel = iSoundlevel;
+ es.m_flSoundTime = soundtime;
+ es.m_bEmitCloseCaption = false;
+ es.m_pSoundName = event->GetParameters();
+
+ C_RecipientFilter filter;
+ C_BaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, es );
+ }
+ break;
+
+ case CChoreoEvent::STOPPOINT:
+ {
+ // Nothing, this is a symbolic event for keeping the vcd alive for ramping out after the last true event
+ //ClearScene();
+ }
+ break;
+
+ case CChoreoEvent::LOOP:
+ ProcessLoop( scene, event );
+ break;
+
+ // Not supported in TF2's model previews
+ case CChoreoEvent::SUBSCENE:
+ case CChoreoEvent::SECTION:
+ {
+ Assert(0);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ if ( !event || !event->GetActive() )
+ return;
+
+ CChoreoActor *actor = event->GetActor();
+ if ( actor && !actor->GetActive() )
+ return;
+
+ CChoreoChannel *channel = event->GetChannel();
+ if ( channel && !channel->GetActive() )
+ return;
+
+ //Msg( "Got ENDEVENT at %.2f\n", currenttime );
+ //Msg( "%8.4f: end %s %i\n", currenttime, event->GetDescription(), event->GetType() );
+
+ switch ( event->GetType() )
+ {
+ case CChoreoEvent::SUBSCENE:
+ {
+ // Not supported in TF2's model previews
+ Assert(0);
+ }
+ break;
+ case CChoreoEvent::SPEAK:
+ {
+ }
+ break;
+ case CChoreoEvent::STOPPOINT:
+ {
+ //SetSequenceLayers( NULL, 0 );
+ //ClearScene();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ if ( !event || !event->GetActive() )
+ return;
+
+ CChoreoActor *actor = event->GetActor();
+ if ( actor && !actor->GetActive() )
+ return;
+
+ CChoreoChannel *channel = event->GetChannel();
+ if ( channel && !channel->GetActive() )
+ return;
+
+ //Msg("PROCESSEVENT at %.2f\n", currenttime );
+
+ switch( event->GetType() )
+ {
+ case CChoreoEvent::EXPRESSION:
+ if ( !m_bShouldRunFlexEvents )
+ {
+ ProcessFlexSettingSceneEvent( scene, event );
+ }
+ break;
+
+ case CChoreoEvent::FLEXANIMATION:
+ if ( m_bShouldRunFlexEvents )
+ {
+ ProcessFlexAnimation( scene, event );
+ }
+ break;
+
+ case CChoreoEvent::SEQUENCE:
+ case CChoreoEvent::SPEAK:
+ case CChoreoEvent::STOPPOINT:
+ // Nothing
+ break;
+
+ // Not supported in TF2's model previews
+ case CChoreoEvent::LOOKAT:
+ case CChoreoEvent::FACE:
+ case CChoreoEvent::SUBSCENE:
+ case CChoreoEvent::MOVETO:
+ case CChoreoEvent::INTERRUPT:
+ case CChoreoEvent::PERMIT_RESPONSES:
+ case CChoreoEvent::GESTURE:
+ Assert(0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Apply a sequence
+// Input : *event -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessSequence( CChoreoScene *scene, CChoreoEvent *event )
+{
+ Assert( event->GetType() == CChoreoEvent::SEQUENCE );
+
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ if ( !event->GetActor() )
+ return;
+
+ int iSequence = LookupSequence( &studioHdr, event->GetParameters() );
+ if (iSequence < 0)
+ return;
+
+ // making sure the mdl has correct playback rate
+ mstudioseqdesc_t &seqdesc = studioHdr.pSeqdesc( iSequence );
+ mstudioanimdesc_t &animdesc = studioHdr.pAnimdesc( studioHdr.iRelativeAnim( iSequence, seqdesc.anim(0,0) ) );
+ m_RootMDL.m_MDL.m_flPlaybackRate = animdesc.fps;
+
+ MDLSquenceLayer_t tmpSequenceLayers[1];
+ tmpSequenceLayers[0].m_nSequenceIndex = iSequence;
+ tmpSequenceLayers[0].m_flWeight = 1.0;
+ tmpSequenceLayers[0].m_bNoLoop = true;
+ tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime;
+ SetSequenceLayers( tmpSequenceLayers, 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *scene -
+// *event -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessLoop( CChoreoScene *scene, CChoreoEvent *event )
+{
+ Assert( event->GetType() == CChoreoEvent::LOOP );
+
+ float backtime = (float)atof( event->GetParameters() );
+
+ bool process = true;
+ int counter = event->GetLoopCount();
+ if ( counter != -1 )
+ {
+ int remaining = event->GetNumLoopsRemaining();
+ if ( remaining <= 0 )
+ {
+ process = false;
+ }
+ else
+ {
+ event->SetNumLoopsRemaining( --remaining );
+ }
+ }
+
+ if ( !process )
+ return;
+
+ //Msg("LOOP: %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() );
+
+ float flPrevTime = m_flSceneTime;
+ scene->LoopToTime( backtime );
+ m_flSceneTime = backtime;
+
+ //Msg(" -> %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() );
+
+ float flDelta = flPrevTime - backtime;
+
+ //Msg(" -> Delta %.2f\n", flDelta );
+
+ // If we're running noloop sequences, we need to push out their begin time, so they keep playing
+ for ( int i = 0; i < m_nNumSequenceLayers; i++ )
+ {
+ if ( m_SequenceLayers[i].m_bNoLoop )
+ {
+ m_SequenceLayers[i].m_flCycleBeganAt += flDelta;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+LocalFlexController_t CTFPlayerModelPanel::GetNumFlexControllers( void )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ return studioHdr.numflexcontrollers();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFPlayerModelPanel::GetFlexDescFacs( int iFlexDesc )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexdesc_t *pflexdesc = studioHdr.pFlexdesc( iFlexDesc );
+
+ return pflexdesc->pszFACS( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFPlayerModelPanel::GetFlexControllerName( LocalFlexController_t iFlexController )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController );
+
+ return pflexcontroller->pszName( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFPlayerModelPanel::GetFlexControllerType( LocalFlexController_t iFlexController )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController );
+
+ return pflexcontroller->pszType( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+LocalFlexController_t CTFPlayerModelPanel::FindFlexController( const char *szName )
+{
+ for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
+ {
+ if (stricmp( GetFlexControllerName( i ), szName ) == 0)
+ {
+ return i;
+ }
+ }
+
+ // AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) );
+ return LocalFlexController_t(-1);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetFlexWeight( LocalFlexController_t index, float value )
+{
+ if (index >= 0 && index < GetNumFlexControllers())
+ {
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index );
+
+ if (pflexcontroller->max != pflexcontroller->min)
+ {
+ value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min);
+ value = clamp( value, 0.0f, 1.0f );
+ }
+
+ m_flexWeight[ index ] = value;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayerModelPanel::GetFlexWeight( LocalFlexController_t index )
+{
+ if (index >= 0 && index < GetNumFlexControllers())
+ {
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index );
+
+ if (pflexcontroller->max != pflexcontroller->min)
+ {
+ return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min;
+ }
+
+ return m_flexWeight[index];
+ }
+ return 0.0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: During paint, apply the flex weights to the model
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetupFlexWeights( void )
+{
+ if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
+ return;
+
+ // initialize the models local to global flex controller mappings
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ if (studioHdr.pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1)
+ {
+ for ( LocalFlexController_t i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++)
+ {
+ int j = C_BaseFlex::AddGlobalFlexController( studioHdr.pFlexcontroller( i )->pszName() );
+ studioHdr.pFlexcontroller( i )->localToGlobal = j;
+ }
+ }
+
+ int iControllers = GetNumFlexControllers();
+ for ( int j = 0; j < iControllers; j++ )
+ {
+ m_RootMDL.m_MDL.m_pFlexControls[j] = 0;
+ }
+
+ LocalFlexController_t i;
+
+ // Decay to neutral
+ for ( i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
+ {
+ SetFlexWeight( i, GetFlexWeight( i ) * 0.95 );
+ }
+
+ // Run scene
+ if ( m_pScene )
+ {
+ m_bShouldRunFlexEvents = true;
+ m_pScene->Think( m_flSceneTime );
+ }
+
+ // get the networked flexweights and convert them from 0..1 to real dynamic range
+ for (i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++)
+ {
+ mstudioflexcontroller_t *pflex = studioHdr.pFlexcontroller( i );
+
+ m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_flexWeight[i];
+ // rescale
+ m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min;
+ }
+
+ if ( m_pScene )
+ {
+ m_bShouldRunFlexEvents = false;
+ m_pScene->Think( m_flSceneTime );
+ }
+
+ ProcessVisemes( m_PhonemeClasses );
+
+ if ( m_pScene )
+ {
+ // Advance time
+ if ( m_flLastTickTime < FLT_EPSILON )
+ {
+ m_flLastTickTime = m_RootMDL.m_MDL.m_flTime - 0.1;
+ }
+
+ m_flSceneTime += (m_RootMDL.m_MDL.m_flTime - m_flLastTickTime);
+ m_flLastTickTime = m_RootMDL.m_MDL.m_flTime;
+
+ if ( m_flSceneEndTime > FLT_EPSILON && m_flSceneTime > m_flSceneEndTime )
+ {
+ bool bLoopScene = m_bLoopScene;
+ char filename[MAX_PATH];
+ V_strcpy_safe( filename, m_pScene->GetFilename() );
+
+ SetSequenceLayers( NULL, 0 );
+ ClearScene();
+
+ if ( bLoopScene )
+ {
+ m_pScene = LoadSceneForModel( filename, this, &m_flSceneEndTime );
+ }
+ else
+ {
+ m_pszVCD = NULL;
+ }
+ }
+ }
+}
+
+extern CFlexSceneFileManager g_FlexSceneFileManager;
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessExpression( CChoreoScene *scene, CChoreoEvent *event )
+{
+ // Flexanimations have to have an end time!!!
+ if ( !event->HasEndTime() )
+ return;
+
+ // Look up the actual strings
+ const char *scenefile = event->GetParameters();
+ const char *name = event->GetParameters2();
+
+ // Have to find both strings
+ if ( scenefile && name )
+ {
+ // Find the scene file
+ const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
+ if ( pExpHdr )
+ {
+ float scenetime = scene->GetTime();
+
+ float flIntensity = event->GetIntensity( scenetime );
+
+ int i;
+ const flexsetting_t *pSetting = NULL;
+
+ // Find the named setting in the base
+ for ( i = 0; i < pExpHdr->numflexsettings; i++ )
+ {
+ pSetting = pExpHdr->pSetting( i );
+ if ( !pSetting )
+ continue;
+
+ if ( !V_stricmp( pSetting->pszName(), name ) )
+ break;
+ }
+
+ if ( i>=pExpHdr->numflexsettings )
+ return;
+
+ flexweight_t *pWeights = NULL;
+ int truecount = pSetting->psetting( (byte *)pExpHdr, 0, &pWeights );
+ if ( !pWeights )
+ return;
+
+ for (i = 0; i < truecount; i++, pWeights++)
+ {
+ int j = FlexControllerLocalToGlobal( pExpHdr, pWeights->key );
+
+ float s = clamp( pWeights->influence * flIntensity, 0.0f, 1.0f );
+ m_RootMDL.m_MDL.m_pFlexControls[ j ] = m_RootMDL.m_MDL.m_pFlexControls[j] * (1.0f - s) + pWeights->weight * s;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessFlexSettingSceneEvent( CChoreoScene *scene, CChoreoEvent *event )
+{
+ // Flexanimations have to have an end time!!!
+ if ( !event->HasEndTime() )
+ return;
+
+ VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" );
+
+ // Look up the actual strings
+ const char *scenefile = event->GetParameters();
+ const char *name = event->GetParameters2();
+
+ // Have to find both strings
+ if ( scenefile && name )
+ {
+ // Find the scene file
+ const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
+ if ( pExpHdr )
+ {
+ float scenetime = scene->GetTime();
+
+ float scale = event->GetIntensity( scenetime );
+
+ // Add the named expression
+ AddFlexSetting( name, scale, pExpHdr );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *expr -
+// scale -
+// *pSettinghdr -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::AddFlexSetting( const char *expr, float scale, const flexsettinghdr_t *pSettinghdr )
+{
+ int i;
+ const flexsetting_t *pSetting = NULL;
+
+ // Find the named setting in the base
+ for ( i = 0; i < pSettinghdr->numflexsettings; i++ )
+ {
+ pSetting = pSettinghdr->pSetting( i );
+ if ( !pSetting )
+ continue;
+
+ const char *name = pSetting->pszName();
+
+ if ( !V_stricmp( name, expr ) )
+ break;
+ }
+
+ if ( i>=pSettinghdr->numflexsettings )
+ {
+ return;
+ }
+
+ flexweight_t *pWeights = NULL;
+ int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights );
+ if ( !pWeights )
+ return;
+
+ for (i = 0; i < truecount; i++, pWeights++)
+ {
+ // Translate to local flex controller
+ // this is translating from the settings's local index to the models local index
+ int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key );
+
+ // blend scaled weighting in to total (post networking g_flexweight!!!!)
+ float s = clamp( scale * pWeights->influence, 0.0f, 1.0f );
+ m_RootMDL.m_MDL.m_pFlexControls[index] = m_RootMDL.m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s;
+
+ for ( int iMergeMDL=0; iMergeMDL<m_aMergeMDLs.Count(); ++iMergeMDL )
+ {
+ m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] = m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Apply flexanimation to actor's face
+// Input : *event -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event )
+{
+ Assert( event->GetType() == CChoreoEvent::FLEXANIMATION );
+
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ CStudioHdr *hdr = &studioHdr;
+ if ( !hdr )
+ return;
+
+ if ( !event->GetTrackLookupSet() )
+ {
+ // Create lookup data
+ for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
+ {
+ CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
+ if ( !track )
+ continue;
+
+ if ( track->IsComboType() )
+ {
+ char name[ 512 ];
+ Q_strncpy( name, "right_" ,sizeof(name));
+ Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
+
+ track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 );
+
+ Q_strncpy( name, "left_" ,sizeof(name));
+ Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
+
+ track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 );
+ }
+ else
+ {
+ track->SetFlexControllerIndex( MAX( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 );
+ }
+ }
+
+ event->SetTrackLookupSet( true );
+ }
+
+ float scenetime = scene->GetTime();
+
+ float weight = event->GetIntensity( scenetime );
+
+ // Iterate animation tracks
+ for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
+ {
+ CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
+ if ( !track )
+ continue;
+
+ // Disabled
+ if ( !track->IsTrackActive() )
+ continue;
+
+ // Map track flex controller to global name
+ if ( track->IsComboType() )
+ {
+ for ( int side = 0; side < 2; side++ )
+ {
+ LocalFlexController_t controller = track->GetRawFlexControllerIndex( side );
+
+ // Get spline intensity for controller
+ float flIntensity = track->GetIntensity( scenetime, side );
+ if ( controller >= LocalFlexController_t(0) )
+ {
+ float orig = GetFlexWeight( controller );
+ float value = orig * (1 - weight) + flIntensity * weight;
+ SetFlexWeight( controller, value );
+ }
+ }
+ }
+ else
+ {
+ LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 );
+
+ // Get spline intensity for controller
+ float flIntensity = track->GetIntensity( scenetime, 0 );
+ if ( controller >= LocalFlexController_t(0) )
+ {
+ float orig = GetFlexWeight( controller );
+ float value = orig * (1 - weight) + flIntensity * weight;
+ SetFlexWeight( controller, value );
+ }
+ }
+ }
+}
+
+extern ConVar g_CV_PhonemeDelay;
+extern ConVar g_CV_PhonemeFilter;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessVisemes( Emphasized_Phoneme *classes )
+{
+ // Any sounds being played?
+ if ( !MouthInfo().IsActive() )
+ return;
+
+ // Multiple phoneme tracks can overlap, look across all such tracks.
+ for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ )
+ {
+ CVoiceData *vd = MouthInfo().GetVoiceSource( source );
+ if ( !vd || vd->ShouldIgnorePhonemes() )
+ continue;
+
+ CSentence *sentence = engine->GetSentence( vd->GetSource() );
+ if ( !sentence )
+ continue;
+
+ float sentence_length = engine->GetSentenceLength( vd->GetSource() );
+ float timesincestart = vd->GetElapsedTime();
+
+ // This sound should be done...why hasn't it been removed yet???
+ if ( timesincestart >= ( sentence_length + 2.0f ) )
+ continue;
+
+ // Adjust actual time
+ float t = timesincestart - g_CV_PhonemeDelay.GetFloat();
+
+ // Get box filter duration
+ float dt = g_CV_PhonemeFilter.GetFloat();
+
+ // Streaming sounds get an additional delay...
+ /*
+ // Tracker 20534: Probably not needed any more with the async sound stuff that
+ // we now have (we don't have a disk i/o hitch on startup which might have been
+ // messing up the startup timing a bit )
+ bool streaming = engine->IsStreaming( vd->m_pAudioSource );
+ if ( streaming )
+ {
+ t -= g_CV_PhonemeDelayStreaming.GetFloat();
+ }
+ */
+
+ // Assume sound has been playing for a while...
+ bool juststarted = false;
+
+ // Get intensity setting for this time (from spline)
+ float emphasis_intensity = sentence->GetIntensity( t, sentence_length );
+
+ // Blend and add visemes together
+ AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted )
+{
+ int pcount = sentence->GetRuntimePhonemeCount();
+ for ( int k = 0; k < pcount; k++ )
+ {
+ const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k );
+
+ if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime())
+ {
+ bool bCrossfade = true;
+ if (bCrossfade)
+ {
+ if (k < pcount-1)
+ {
+ const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 );
+ // if I have a neighbor
+ if ( next )
+ {
+ // and they're touching
+ if (next->GetStartTime() == phoneme->GetEndTime() )
+ {
+ // no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme
+ dt = MAX( dt, MIN( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
+ }
+ else
+ {
+ // dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme
+ dt = MAX( dt, MIN( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
+ }
+ }
+ else
+ {
+ // last phoneme in list, increase the blend length to the length of the current phoneme
+ dt = MAX( dt, phoneme->GetEndTime() - phoneme->GetStartTime() );
+ }
+ }
+ }
+ }
+
+ float t1 = ( phoneme->GetStartTime() - t) / dt;
+ float t2 = ( phoneme->GetEndTime() - t) / dt;
+
+ if (t1 < 1.0 && t2 > 0)
+ {
+ float scale;
+
+ // clamp
+ if (t2 > 1)
+ t2 = 1;
+ if (t1 < 0)
+ t1 = 0;
+
+ // FIXME: simple box filter. Should use something fancier
+ scale = (t2 - t1);
+
+ AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *classes -
+// phoneme -
+// scale -
+// newexpression -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ CStudioHdr *hdr = &studioHdr;
+ if ( !hdr )
+ return;
+
+ int type;
+
+ // Setup weights for any emphasis blends
+ bool skip = SetupEmphasisBlend( classes, phoneme );
+
+ phoneme = 230;
+ scale = 1.0;
+
+ // Uh-oh, missing or unknown phoneme???
+ if ( skip )
+ {
+ return;
+ }
+
+ // Compute blend weights
+ ComputeBlendedSetting( classes, emphasis_intensity );
+
+ for ( type = 0; type < NUM_PHONEME_CLASSES; type++ )
+ {
+ Emphasized_Phoneme *info = &classes[ type ];
+ if ( !info->valid || info->amount == 0.0f )
+ continue;
+
+ const flexsettinghdr_t *actual_flexsetting_header = info->base;
+ const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme );
+ if (!pSetting)
+ {
+ continue;
+ }
+
+ flexweight_t *pWeights = NULL;
+
+ int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights );
+ if ( pWeights )
+ {
+ for ( int i = 0; i < truecount; i++)
+ {
+ // Translate to global controller number
+ int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key );
+ // Add scaled weighting in
+ if ( pWeights->weight > 0 )
+ {
+ m_RootMDL.m_MDL.m_pFlexControls[j] += info->amount * scale * pWeights->weight;
+ }
+ // Go to next setting
+ pWeights++;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A lot of the one time setup and also resets amount to 0.0f default
+// for strong/weak/normal tracks
+// Returning true == skip this phoneme
+// Input : *classes -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme )
+{
+ int i;
+
+ bool skip = false;
+
+ for ( i = 0; i < NUM_PHONEME_CLASSES; i++ )
+ {
+ Emphasized_Phoneme *info = &classes[ i ];
+
+ // Assume it's bogus
+ info->valid = false;
+ info->amount = 0.0f;
+
+ // One time setup
+ if ( !info->basechecked )
+ {
+ info->basechecked = true;
+ info->base = (flexsettinghdr_t *)g_FlexSceneFileManager.FindSceneFile( this, info->classname, false );
+ }
+ info->exp = NULL;
+ if ( info->base )
+ {
+ Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') );
+ info->exp = info->base->pIndexedSetting( phoneme );
+ }
+
+ if ( info->required && ( !info->base || !info->exp ) )
+ {
+ skip = true;
+ break;
+ }
+
+ if ( info->exp )
+ {
+ info->valid = true;
+ }
+ }
+
+ return skip;
+}
+
+#define STRONG_CROSSFADE_START 0.60f
+#define WEAK_CROSSFADE_START 0.40f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Here's the formula
+// 0.5 is neutral 100 % of the default setting
+// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END
+// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START
+// so we don't get huge numbers
+// Input : *classes -
+// emphasis_intensity -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity )
+{
+ // See which blends are available for the current phoneme
+ bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid;
+ bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid;
+
+ // Better have phonemes in general
+ Assert( classes[ PHONEME_CLASS_NORMAL ].valid );
+
+ if ( emphasis_intensity > STRONG_CROSSFADE_START )
+ {
+ if ( has_strong )
+ {
+ // Blend in some of strong
+ float dist_remaining = 1.0f - emphasis_intensity;
+ float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START );
+
+ classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START;
+ classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac;
+ }
+ else
+ {
+ emphasis_intensity = MIN( emphasis_intensity, STRONG_CROSSFADE_START );
+ classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
+ }
+ }
+ else if ( emphasis_intensity < WEAK_CROSSFADE_START )
+ {
+ if ( has_weak )
+ {
+ // Blend in some weak
+ float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity;
+ float frac = dist_remaining / ( WEAK_CROSSFADE_START );
+
+ classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START;
+ classes[ PHONEME_CLASS_WEAK ].amount = frac;
+ }
+ else
+ {
+ emphasis_intensity = MAX( emphasis_intensity, WEAK_CROSSFADE_START );
+ classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
+ }
+ }
+ else
+ {
+ // Assume 0.5 (neutral) becomes a scaling of 1.0f
+ classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::InitPhonemeMappings( void )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ if ( studioHdr.IsValid() )
+ {
+ char szBasename[MAX_PATH];
+ Q_StripExtension( studioHdr.pszName(), szBasename, sizeof( szBasename ) );
+
+ char szExpressionName[MAX_PATH];
+ Q_snprintf( szExpressionName, sizeof( szExpressionName ), "%s/phonemes/phonemes", szBasename );
+ if ( g_FlexSceneFileManager.FindSceneFile( this, szExpressionName, false ) )
+ {
+ SetupMappings( szExpressionName );
+ return;
+ }
+ }
+
+ SetupMappings( "phonemes" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetupMappings( char const *pchFileRoot )
+{
+ // Fill in phoneme class lookup
+ memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) );
+
+ Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ];
+ Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot );
+ normal->required = true;
+
+ Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ];
+ Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot );
+ Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ];
+ Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but
+// we just do this in memory with an array of integers (could be shorts, I suppose)
+// Input : *pSettinghdr -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EnsureTranslations( const flexsettinghdr_t *pSettinghdr )
+{
+ Assert( pSettinghdr );
+
+ FS_LocalToGlobal_t entry( pSettinghdr );
+
+ unsigned short idx = m_LocalToGlobal.Find( entry );
+ if ( idx != m_LocalToGlobal.InvalidIndex() )
+ return;
+
+ entry.SetCount( pSettinghdr->numkeys );
+
+ for ( int i = 0; i < pSettinghdr->numkeys; ++i )
+ {
+ entry.m_Mapping[ i ] = C_BaseFlex::AddGlobalFlexController( pSettinghdr->pLocalName( i ) );
+ }
+
+ m_LocalToGlobal.Insert( entry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Look up instance specific mapping
+// Input : *pSettinghdr -
+// key -
+// Output : int
+//-----------------------------------------------------------------------------
+int CTFPlayerModelPanel::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key )
+{
+ FS_LocalToGlobal_t entry( pSettinghdr );
+
+ int idx = m_LocalToGlobal.Find( entry );
+ if ( idx == m_LocalToGlobal.InvalidIndex() )
+ {
+ // This should never happen!!!
+ Assert( 0 );
+ Warning( "Unable to find mapping for flexcontroller %i, settings %p on CTFPlayerModelPanel\n", key, pSettinghdr );
+ EnsureTranslations( pSettinghdr );
+ idx = m_LocalToGlobal.Find( entry );
+ if ( idx == m_LocalToGlobal.InvalidIndex() )
+ {
+ Error( "CTFPlayerModelPanel::FlexControllerLocalToGlobal failed!\n" );
+ }
+ }
+
+ FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ];
+ // Validate lookup
+ Assert( result.m_nCount != 0 && key < result.m_nCount );
+ int index = result.m_Mapping[ key ];
+ return index;
+}
+
diff --git a/game/client/tf/vgui/tf_playermodelpanel.h b/game/client/tf/vgui/tf_playermodelpanel.h
new file mode 100644
index 0000000..e14375b
--- /dev/null
+++ b/game/client/tf/vgui/tf_playermodelpanel.h
@@ -0,0 +1,222 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_PLAYERMODELPANEL_H
+#define TF_PLAYERMODELPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "basemodel_panel.h"
+#include "ichoreoeventcallback.h"
+
+class CChoreoScene;
+
+extern CMouthInfo g_ClientUIMouth;
+
+// A model panel that knows how to imitate a TF2 player, including wielding/wearing unlockable items.
+class CTFPlayerModelPanel : public CBaseModelPanel, public IChoreoEventCallback, public IHasLocalToGlobalFlexSettings, public IModelLoadCallback
+{
+ DECLARE_CLASS_SIMPLE( CTFPlayerModelPanel, CBaseModelPanel );
+public:
+ CTFPlayerModelPanel( vgui::Panel *pParent, const char *pName );
+ ~CTFPlayerModelPanel( void );
+
+ void ApplySettings( KeyValues *inResourceData );
+
+ void SetToPlayerClass( int iClass, bool bIsRobot, bool bForceRefresh = false );
+ bool HoldItemInSlot( int iSlot );
+ bool HoldItem( int iItemNumber );
+ void SwitchHeldItemTo( CEconItemView *pItem );
+ void EquipRequiredLoadoutSlot( int iRequiredLoadoutSlot );
+ CEconItemView *GetHeldItem() { return m_pHeldItem; }
+
+ int AddCarriedItem( CEconItemView *pItem );
+ void ClearCarriedItems( void );
+
+ void PlayVCD( const char *pszVCD, const char *pszWeaponEntityRequired = NULL, bool bLoopVCD = true, bool bFileNameOnly = true );
+
+ // Handle animation events
+ virtual void FireEvent( const char *pszEventName, const char *pszEventOptions );
+
+ const CUtlVector<CEconItemView*> &GetCarriedItems() { return m_ItemsToCarry; }
+ int GetNumCarriedItems() const { return m_ItemsToCarry.Count(); }
+ int GetPlayerClass() const { return m_iCurrentClassIndex; }
+ Vector GetZoomOffset();
+
+ void ToggleZoom();
+ bool IsZoomed() { return m_bZoomedToHead; }
+
+ void SetTeam( int iTeam );
+ int GetTeam( void ) { return m_iTeam; }
+
+ void UpdatePreviewVisuals( void );
+
+ // From IChoreoEventCallback
+ virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event );
+ virtual void EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event );
+ virtual void ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event );
+ virtual bool CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event );
+ virtual void SetupFlexWeights( void );
+
+ // IHasLocalToGlobalFlexSettings
+ virtual void EnsureTranslations( const flexsettinghdr_t *pSettinghdr );
+ int FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key );
+
+ // IModelLoadCallback
+ virtual void OnModelLoadComplete( const model_t *pModel );
+
+ void SetEyeGlowEffect ( const char *pEffectName, Vector vColor1, Vector vColor2, bool bForceUpdate, bool bPlaySparks );
+
+ void InvalidateParticleEffects();
+
+protected:
+ // From CBaseModelPanel
+ virtual void PrePaint3D( IMatRenderContext *pRenderContext ) OVERRIDE;
+ virtual void PostPaint3D( IMatRenderContext *pRenderContext ) OVERRIDE;
+ virtual void RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix );
+ virtual void RenderingMergedModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix );
+ virtual IMaterial* GetOverrideMaterial( MDLHandle_t mdlHandle ) OVERRIDE;
+
+private:
+
+ enum modelpanel_particle_system_t
+ {
+ SYSTEM_HEAD = 0,
+ SYSTEM_MISC1,
+ SYSTEM_MISC2,
+ SYSTEM_WEAPON, // there can only be one weapon equipped
+ SYSTEM_ACTIONSLOT,
+ SYSTEM_EYEGLOW_LEFT,
+ SYSTEM_EYEGLOW_RIGHT,
+ SYSTEM_EYESPARK_LEFT,
+ SYSTEM_EYESPARK_RIGHT,
+ SYSTEM_TAUNT,
+ SYSTEM_COUNT,
+ };
+
+ // Choreo Scene handling
+ void ClearScene( void );
+ void ProcessSequence( CChoreoScene *scene, CChoreoEvent *event );
+ void ProcessExpression( CChoreoScene *scene, CChoreoEvent *event );
+ void ProcessFlexSettingSceneEvent( CChoreoScene *scene, CChoreoEvent *event );
+ void ProcessLoop( CChoreoScene *scene, CChoreoEvent *event );
+ void AddFlexSetting( const char *expr, float scale, const flexsettinghdr_t *pSettinghdr );
+ void ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event );
+ void SetFlexWeight( LocalFlexController_t index, float value );
+ float GetFlexWeight( LocalFlexController_t index );
+
+ LocalFlexController_t GetNumFlexControllers( void );
+ const char *GetFlexDescFacs( int iFlexDesc );
+ const char *GetFlexControllerName( LocalFlexController_t iFlexController );
+ const char *GetFlexControllerType( LocalFlexController_t iFlexController );
+ LocalFlexController_t FindFlexController( const char *szName );
+
+ // Mouth processing
+ CMouthInfo& MouthInfo() { return g_ClientUIMouth; }
+ void ProcessVisemes( Emphasized_Phoneme *classes );
+ void AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted );
+ void AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression );
+ bool SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme );
+ void ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity );
+ void InitPhonemeMappings( void );
+ void SetupMappings( char const *pchFileRoot );
+
+ void HoldFirstValidItem( void );
+ void EquipAllWearables( CEconItemView *pHeldItem );
+ void EquipItem( CEconItemView *pItem );
+ bool UpdateHeldItem( int iDesiredSlot );
+ void UpdateWeaponBodygroups( bool bModifyDeployedOnlyBodygroups );
+ void UpdateHiddenBodyGroups( CEconItemView* pItem );
+ CEconItemView *GetItemInSlot( int iSlot );
+ CEconItemView *GetPreviewItem( CEconItemView *pMatchItem );
+
+ // Use this instead of SetMergeModel() - handles dynamic asset allocation
+ void LoadAndAttachAdditionalModel( const char *pMDLName, CEconItemView *pItem );
+ bool FinishAttachAdditionalModel( const model_t *pModel );
+ void RemoveAdditionalModels( void );
+
+ bool UpdateCosmeticParticles(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix,
+ modelpanel_particle_system_t iSystem,
+ CEconItemView *pEconItem
+ );
+
+ void UpdateEyeGlows( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix, bool bIsRightEye );
+ void UpdateActionSlotEffects( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix );
+ void UpdateTauntEffects( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix );
+
+ int m_iCurrentClassIndex;
+ int m_iCurrentSlotIndex;
+ CUtlVector<CEconItemView*> m_ItemsToCarry; // Items that our player should be seen carrying
+ QAngle m_angPlayerOrg;
+
+ int m_nBody;
+ int m_iTeam;
+ bool m_bZoomedToHead;
+
+ MDLHandle_t m_MergeMDL;
+
+ CEconItemView *m_pHeldItem;
+
+ const char *m_pszVCD;
+ const char *m_pszWeaponEntityRequired;
+ bool m_bLoopVCD;
+ bool m_bVCDFileNameOnly;
+
+ CChoreoScene *m_pScene;
+ float m_flSceneTime;
+ float m_flSceneEndTime;
+ float m_flLastTickTime;
+ bool m_bLoopScene;
+
+ CUtlVector< CRefCountedModelIndex > m_vecDynamicAssetsLoaded;
+ CUtlVector< CEconItemView* > m_vecItemsLoaded;
+
+ struct CustomClassData_t
+ {
+ float m_flFOV;
+ Vector m_vPosition;
+ QAngle m_vAngles;
+ };
+ CUtlVector< CustomClassData_t > m_customClassData;
+
+ // Choreo scenes
+ bool m_bShouldRunFlexEvents;
+ float m_flexWeight[ MAXSTUDIOFLEXCTRL ];
+ Emphasized_Phoneme m_PhonemeClasses[ NUM_PHONEME_CLASSES ];
+ CUtlRBTree< FS_LocalToGlobal_t, unsigned short > m_LocalToGlobal;
+
+ // The particle system to draw
+ particle_data_t *m_aParticleSystems[ SYSTEM_COUNT ];
+
+ bool m_bUpdateEyeGlows;
+ bool m_bPlaySparks;
+
+ char m_pszEyeGlowParticleName[MAX_PATH];
+ Vector m_vEyeGlowColor1;
+ Vector m_vEyeGlowColor2;
+ bool m_bDrawActionSlotEffects;
+ bool m_bDrawTauntParticles;
+
+ float m_flTauntParticleRefireTime;
+ float m_flTauntParticleRefireRate;
+
+ bool m_bIsRobot;
+
+ CPanelAnimationVar( bool, m_bDisableSpeakEvent, "disable_speak_event", "0" );
+
+ CEconItemView *GetLoadoutItemFromMDLHandle( loadout_positions_t iPosition, MDLHandle_t mdlHandle );
+ bool RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix );
+ MDLData_t m_StatTrackModel;
+ float m_flStatTrackScale;
+};
+
+#endif // TF_PLAYERMODELPANEL_H
diff --git a/game/client/tf/vgui/tf_playerpanel.cpp b/game/client/tf/vgui/tf_playerpanel.cpp
new file mode 100644
index 0000000..c541922
--- /dev/null
+++ b/game/client/tf/vgui/tf_playerpanel.cpp
@@ -0,0 +1,411 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hud.h"
+#include "c_team.h"
+#include "tf_shareddefs.h"
+#include "tf_gamerules.h"
+#include "tf_hud_objectivestatus.h"
+#include "tf_hud_statpanel.h"
+#include "iclientmode.h"
+#include "c_playerresource.h"
+#include "tf_hud_building_status.h"
+#include "tf_hud_mann_vs_machine_status.h"
+#include "tf_hud_tournament.h"
+#include "tf_hud_winpanel.h"
+#include "tf_tips.h"
+#include "tf_mapinfomenu.h"
+#include "econ_wearable.h"
+#include "c_tf_playerresource.h"
+#include "playerspawncache.h"
+#include "econ_notifications.h"
+#include <spectatorgui.h>
+#include "hudelement.h"
+#include "tf_spectatorgui.h"
+#include "tf_hud_playerstatus.h"
+#include "item_model_panel.h"
+#include "tf_gamerules.h"
+#include "tf_playerpanel.h"
+
+#include "tf_gc_client.h"
+#include "tf_lobby_server.h"
+
+#include <vgui/ILocalize.h>
+#include <vgui/ISurface.h>
+#include <VGuiMatSurface/IMatSystemSurface.h>
+#include "vgui_avatarimage.h"
+
+using namespace vgui;
+
+extern ConVar tf_max_health_boost;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerPanel::CTFPlayerPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name )
+{
+ m_pHealthIcon = new CTFPlayerPanelGUIHealth( this, "HealthIcon" );
+ m_pClassImage = NULL;
+ m_bPlayerReadyModeActive = false;
+ m_pReadyBG = new ScalableImagePanel( this , "ReadyBG" );
+ m_pReadyImage = new ImagePanel( this, "ReadyImage" );
+
+ SetDialogVariable( "chargeamount", "" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerPanel::Reset( void )
+{
+ m_pHealthIcon->Reset();
+ m_iPrevHealth = -999;
+ m_iPrevClass = -999;
+ m_bPrevAlive = false;
+ m_iPrevRespawnWait = -999;
+ m_iPrevCharge = -1;
+ m_bPrevReady = true;
+ m_iPrevState = GR_STATE_PREGAME;
+ m_bPlayerReadyModeActive = false;
+ m_nGCTeam = TEAM_INVALID;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerPanel::Update( void )
+{
+ if ( !g_TF_PR || !TFGameRules() )
+ return false;
+
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( !pLocalPlayer )
+ return false;
+
+ bool bChanged = false;
+ bool bObserver = pLocalPlayer->GetObserverMode() != OBS_MODE_NONE;
+ bool bVisible = GetTeam() >= FIRST_GAME_TEAM;
+ int iRespawnWait = -1;
+ m_bPlayerReadyModeActive = ( !bObserver &&
+ TFGameRules()->UsePlayerReadyStatusMode() &&
+ TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS );
+
+ CTFGSLobby *pLobby = GTFGCClientSystem()->GetLobby();
+ if ( pLobby )
+ {
+ const CTFLobbyMember *pMember = pLobby->GetMemberDetails( m_steamID );
+ if ( pMember )
+ {
+ // Keep this updated
+ m_nGCTeam = pMember->team();
+
+ if ( !m_iPlayerIndex && pMember->has_last_connect_time() )
+ {
+ iRespawnWait = CRTime::RTime32DateAdd( pMember->last_connect_time(), 180, k_ETimeUnitSecond ) - CRTime::RTime32TimeCur();
+ if ( iRespawnWait <= 0 )
+ iRespawnWait = -1;
+ }
+ }
+ }
+
+ if ( IsVisible() != bVisible )
+ {
+ SetVisible( bVisible );
+ bChanged = true;
+ }
+
+ if ( bVisible )
+ {
+ if ( g_TF_PR )
+ {
+ // Are we connected with a class?
+ Assert( TF_CLASS_UNDEFINED == 0 );
+ int iClass = -1;
+ bool bAlive = false;
+ const int k_Health_Dead = -1;
+ const int k_Health_NotINGame = -2;
+ int iHealth = k_Health_NotINGame;
+ if ( m_iPlayerIndex > 0 )
+ {
+ iClass = g_TF_PR->GetPlayerClass( m_iPlayerIndex );
+
+ if ( iClass != TF_CLASS_UNDEFINED )
+ {
+ bAlive = g_TF_PR->IsAlive( m_iPlayerIndex );
+ iHealth = k_Health_Dead;
+ }
+ if ( bAlive )
+ iHealth = g_TF_PR->GetHealth( m_iPlayerIndex );
+
+ // Calc respawn time remaining
+ if ( iClass != TF_CLASS_UNDEFINED && !bAlive )
+ {
+ float flRespawnAt = g_TF_PR->GetNextRespawnTime( m_iPlayerIndex );
+ iRespawnWait = (flRespawnAt - gpGlobals->curtime);
+ if ( iRespawnWait <= 0 )
+ iRespawnWait = -1;
+ }
+
+ // Hide class info from the other team?
+ if ( !bObserver &&
+ TFGameRules()->IsCompetitiveMode() &&
+ GetTeam() != g_TF_PR->GetTeam( pLocalPlayer->entindex() ) )
+ {
+ iClass = TF_CLASS_UNDEFINED;
+ }
+ }
+
+ // Update live state
+ if ( m_pClassImage )
+ {
+ if ( m_bPrevAlive != bAlive )
+ {
+ bChanged = true;
+ m_bPrevAlive = bAlive;
+ if ( bAlive )
+ {
+ m_pClassImage->SetDrawColor( Color( 255, 255, 255, 255 ) );
+ }
+ else
+ {
+ m_pClassImage->SetDrawColor( Color( 96, 96, 96, 255 ) );
+ }
+
+ UpdateBorder();
+ }
+
+ // Update class image
+ if ( m_iPrevClass != iClass )
+ {
+ bChanged = true;
+ m_iPrevClass = iClass;
+ if ( iClass < 0 )
+ {
+ m_pClassImage->SetImage( "hud_connecting" );
+ }
+ else if ( iClass == TF_CLASS_UNDEFINED )
+ {
+ m_pClassImage->SetImage( "hud_class_not_chosen" );
+ }
+ else
+ {
+ m_pClassImage->SetImage( VarArgs( "%s_alpha", ( GetTeam() == TF_TEAM_RED ) ? g_pszItemClassImagesRed[iClass] : g_pszItemClassImagesBlue[iClass] ) );
+ }
+ }
+ }
+
+ // update health indicator
+ if ( iHealth != m_iPrevHealth && m_pHealthIcon )
+ {
+ m_iPrevHealth = iHealth;
+ if ( iHealth == k_Health_NotINGame )
+ {
+ m_pHealthIcon->SetVisible( false );
+ }
+ else
+ {
+ m_pHealthIcon->SetVisible( true );
+ if ( iHealth < 0 )
+ {
+ Assert( iHealth == k_Health_Dead );
+ m_pHealthIcon->SetHealth( -1, 1, 1 );
+ }
+ else
+ {
+ float flMaxHealth = g_TF_PR->GetMaxHealth( m_iPlayerIndex );
+ float iMaxBuffedHealth = flMaxHealth * tf_max_health_boost.GetFloat(); // Hacky, but it'll work.
+ m_pHealthIcon->SetHealth( iHealth, flMaxHealth, iMaxBuffedHealth );
+ bChanged = true;
+ }
+ }
+ }
+
+ // Update respawn time text
+ if ( iRespawnWait != m_iPrevRespawnWait )
+ {
+ m_iPrevRespawnWait = iRespawnWait;
+ if ( iRespawnWait < 0 )
+ {
+ SetDialogVariable( "respawntime", "" );
+ }
+ else
+ {
+ SetDialogVariable( "respawntime", VarArgs( "%d s", iRespawnWait ) );
+ bChanged = true;
+ }
+ }
+
+ bool bReadyMode = TFGameRules()->UsePlayerReadyStatusMode();
+
+ int iCharge = ( iClass == TF_CLASS_MEDIC ) ? g_TF_PR->GetChargeLevel( m_iPlayerIndex ) : 0;
+ if ( iCharge != m_iPrevCharge )
+ {
+ if ( iCharge > 0 && !( bReadyMode && !bObserver ) )
+ {
+ SetDialogVariable( "chargeamount", VarArgs( "%d%%", iCharge ) );
+ bChanged = true;
+ }
+ else
+ {
+ SetDialogVariable( "chargeamount", "" );
+ }
+ m_iPrevCharge = iCharge;
+ }
+
+ if ( bReadyMode )
+ {
+ bool bPlayerReady = false;
+
+ if ( m_bPlayerReadyModeActive )
+ {
+ if ( m_iPlayerIndex && g_TF_PR->IsConnected( m_iPlayerIndex ) )
+ {
+ bPlayerReady = TFGameRules()->IsPlayerReady( m_iPlayerIndex );
+ }
+ }
+
+ if ( m_pReadyImage && ( m_pReadyImage->IsVisible() != bPlayerReady ) )
+ {
+ m_pReadyImage->SetVisible( bPlayerReady );
+ }
+
+ if ( m_pHealthIcon && ( m_pHealthIcon->IsVisible() == m_bPlayerReadyModeActive ) )
+ {
+ m_pHealthIcon->SetVisible( !m_bPlayerReadyModeActive );
+ }
+
+ if ( m_pReadyBG && ( m_pReadyBG->IsVisible() != m_bPlayerReadyModeActive ) )
+ {
+ m_pReadyBG->SetVisible( m_bPlayerReadyModeActive );
+ }
+
+ if ( TFGameRules()->State_Get() != m_iPrevState )
+ {
+ bChanged = true;
+ }
+ m_iPrevState = TFGameRules()->State_Get();
+ }
+ }
+ }
+
+ return bChanged;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pClassImage = dynamic_cast<CTFClassImage*>( FindChildByName( "classimage" ) );
+
+ Reset();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerPanel::SetPlayerIndex( int iIndex )
+{
+ if ( iIndex <= 0 )
+ {
+ Setup( iIndex, CSteamID(), "" );
+ }
+ else
+ {
+ Setup( iIndex, GetSteamIDForPlayerIndex( iIndex ), g_TF_PR->GetPlayerName( iIndex ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerPanel::Setup( int iPlayerIndex, CSteamID steamID, const char *pszPlayerName, int nLobbyTeam /*= TEAM_INVALID*/ )
+{
+ if ( pszPlayerName == NULL )
+ pszPlayerName = "";
+ if ( m_iPlayerIndex != iPlayerIndex
+ || m_steamID != steamID
+ || Q_strcmp( m_sPlayerName, pszPlayerName ) )
+ {
+ Reset();
+ m_iPlayerIndex = iPlayerIndex;
+ m_steamID = steamID;
+ m_sPlayerName = pszPlayerName;
+ SetDialogVariable( "playername", m_sPlayerName );
+ m_nGCTeam = nLobbyTeam;
+ }
+
+ if ( m_iPlayerIndex > 0 || m_steamID.IsValid() )
+ {
+ UpdateBorder();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerPanel::SetSpecIndex( int iIndex )
+{
+ m_iSpecIndex = iIndex;
+
+ if ( m_iSpecIndex > 0 && m_iSpecIndex <= 12 && !m_bPlayerReadyModeActive )
+ {
+ SetDialogVariable( "specindex", VarArgs( "%d", m_iSpecIndex ) );
+ }
+ else
+ {
+ SetDialogVariable( "specindex", "" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerPanel::UpdateBorder( void )
+{
+ if ( !g_TF_PR )
+ return;
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ if ( !m_bPrevAlive )
+ {
+ SetBorder( pScheme->GetBorder("TFFatLineBorder") );
+ }
+ else
+ {
+ if ( GetTeam() == TF_TEAM_RED )
+ {
+ SetBorder( pScheme->GetBorder( "TFFatLineBorderRedBG" ) );
+ }
+ else
+ {
+ SetBorder( pScheme->GetBorder( "TFFatLineBorderBlueBG" ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerPanel::GetTeam( void )
+{
+ if ( m_nGCTeam != TEAM_INVALID && TFGameRules() )
+ {
+ return TFGameRules()->GetGameTeamForGCTeam( (TF_GC_TEAM)m_nGCTeam );
+ }
+ else if ( GetPlayerIndex() && g_TF_PR )
+ {
+ return g_TF_PR->GetTeam( GetPlayerIndex() );
+ }
+
+ return TEAM_INVALID;
+}
+
+
diff --git a/game/client/tf/vgui/tf_playerpanel.h b/game/client/tf/vgui/tf_playerpanel.h
new file mode 100644
index 0000000..96bc3ab
--- /dev/null
+++ b/game/client/tf/vgui/tf_playerpanel.h
@@ -0,0 +1,78 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef TF_PLAYERPANEL_H
+#define TF_PLAYERPANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_spectatorgui.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Custom health panel used to show spectator target's health in Tournament HUD
+//-----------------------------------------------------------------------------
+class CTFPlayerPanelGUIHealth : public CTFSpectatorGUIHealth
+{
+public:
+ CTFPlayerPanelGUIHealth( Panel *parent, const char *name ) : CTFSpectatorGUIHealth( parent, name )
+ {
+ }
+
+ virtual const char *GetResFilename( void )
+ {
+ return "resource/UI/SpectatorTournamentGUIHealth.res";
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A panel representing a player, shown in tournament mode Spec GUI
+//-----------------------------------------------------------------------------
+class CTFPlayerPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTFPlayerPanel, vgui::EditablePanel );
+public:
+ CTFPlayerPanel( vgui::Panel *parent, const char *name );
+
+ virtual void Reset( void );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ virtual bool Update( void );
+ void SetPlayerIndex( int iIndex );
+ int GetPlayerIndex( void ) { return m_iPlayerIndex; }
+ void Setup( int iPlayerIndex, CSteamID steamID, const char *pszPlayerName, int nLobbyTeam = TEAM_INVALID );
+ void SetSpecIndex( int iIndex );
+ int GetSpecIndex( void ) { return m_iSpecIndex; }
+ virtual void UpdateBorder( void );
+ int GetTeam( void );
+
+ inline CSteamID GetSteamID() const { return m_steamID; }
+
+protected:
+ int m_iPlayerIndex;
+ CUtlString m_sPlayerName;
+ CSteamID m_steamID;
+ int m_iSpecIndex;
+ CTFClassImage *m_pClassImage;
+ CTFPlayerPanelGUIHealth *m_pHealthIcon;
+ int m_iPrevHealth;
+ bool m_bPrevAlive;
+ int m_iPrevClass;
+ int m_iPrevRespawnWait;
+ int m_iPrevCharge;
+ vgui::ScalableImagePanel *m_pReadyBG;
+ vgui::ImagePanel *m_pReadyImage;
+
+ // Ready state
+ bool m_bPrevReady;
+ int m_iPrevState;
+ bool m_bPlayerReadyModeActive;
+ int m_nGCTeam;
+};
+
+
+
+#endif // TF_PLAYERPANEL_H
diff --git a/game/client/tf/vgui/tf_pvp_rank_panel.cpp b/game/client/tf/vgui/tf_pvp_rank_panel.cpp
new file mode 100644
index 0000000..70b6249
--- /dev/null
+++ b/game/client/tf/vgui/tf_pvp_rank_panel.cpp
@@ -0,0 +1,762 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+
+#include "tf_pvp_rank_panel.h"
+#include "tf_matchmaking_shared.h"
+#include "basemodel_panel.h"
+#include "vgui_controls/ProgressBar.h"
+#include "tf_ladder_data.h"
+#include "iclientmode.h"
+#include <vgui_controls/AnimationController.h>
+#include "tf_controls.h"
+#include "vgui/ISurface.h"
+#include "animation.h"
+#include "clientmode_tf.h"
+#include "vgui/IInput.h"
+#include "vgui_controls/MenuItem.h"
+#include "tf_gc_client.h"
+#include "tf_xp_source.h"
+
+using namespace vgui;
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+#ifdef STAGING_ONLY
+ extern ConVar tf_test_pvp_rank_xp_change;
+#endif
+
+ConVar tf_xp_breakdown_interval( "tf_xp_breakdown_interval", "1.85", FCVAR_DEVELOPMENTONLY );
+ConVar tf_xp_breakdown_lifetime( "tf_xp_breakdown_lifetime", "3.5", FCVAR_DEVELOPMENTONLY );
+
+extern const char *s_pszMatchGroups[];
+
+CPvPRankPanel::XPState_t::XPState_t( EMatchGroup eMatchGroup )
+ : m_nStartXP( 0u )
+ , m_nTargetXP( 0u )
+ , m_nActualXP( 0u )
+ , m_bCurrentDeltaViewed( true )
+ , m_eMatchGroup( eMatchGroup )
+{
+ Assert( m_eMatchGroup != k_nMatchGroup_Invalid );
+
+ // Default to level 1's starting XP value
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup );
+ if ( pMatchDesc && pMatchDesc->m_pProgressionDesc )
+ {
+ auto level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( 1 );
+ m_nStartXP = level.m_nStartXP;
+ m_nActualXP = m_nStartXP;
+ m_nTargetXP = m_nStartXP;
+ }
+
+ ListenForGameEvent( "experience_changed" );
+ ListenForGameEvent( "server_spawn" );
+}
+
+void CPvPRankPanel::XPState_t::FireGameEvent( IGameEvent *pEvent )
+{
+ if ( FStrEq( pEvent->GetName(), "experience_changed" ) ) // For changing tf_progression_set_xp_to_level
+ {
+ UpdateXP( false );
+ }
+ else if ( FStrEq( pEvent->GetName(), "server_spawn" ) && GTFGCClientSystem()->BHaveLiveMatch()
+ && GTFGCClientSystem()->GetLiveMatchGroup() == m_eMatchGroup )
+ {
+ UpdateXP( false );
+ // Acknowledge any outstanding XP sources when we start a match. It looks really weird when
+ // you see old match xp sources at the end of a match.
+ GTFGCClientSystem()->AcknowledgePendingXPSources( m_eMatchGroup );
+ }
+}
+
+void CPvPRankPanel::XPState_t::UpdateXP( bool bInitial )
+{
+ uint32 nStartXP = 0u;
+ uint32 nNewXP = 0u;
+
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup );
+ // Should only be creating this object for matches that have progressions
+ Assert( pMatchDesc && pMatchDesc->m_pProgressionDesc );
+ if ( pMatchDesc && pMatchDesc->m_pProgressionDesc )
+ {
+ // Default to level 1's lowest XP value
+ auto level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( 1 );
+ nNewXP = level.m_nStartXP;
+ nStartXP = level.m_nStartXP;
+
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ nStartXP = pMatchDesc->m_pProgressionDesc->GetLocalPlayerLastAckdExperience();
+ // Get the actual user's XP
+ nNewXP = pMatchDesc->m_pProgressionDesc->GetPlayerExperienceBySteamID( steamapicontext->SteamUser()->GetSteamID() );
+ }
+ }
+
+ // If there's no change, don't do anything
+ if ( nNewXP != m_nActualXP || bInitial )
+ {
+ m_nActualXP = nNewXP;
+ m_nStartXP = nStartXP;
+ m_bCurrentDeltaViewed = false;
+ }
+
+ if ( bInitial )
+ {
+ m_progressTimer.Start( 1.f );
+ m_bCurrentDeltaViewed = true;
+ m_nTargetXP = m_nStartXP;
+ }
+}
+
+uint32 CPvPRankPanel::XPState_t::GetCurrentXP() const
+{
+ float flTimeProgress = 0.f;
+ if ( m_progressTimer.HasStarted() )
+ {
+ flTimeProgress = Clamp( m_progressTimer.GetElapsedTime(), 0.f, m_progressTimer.GetCountdownDuration() );
+ flTimeProgress = Gain( flTimeProgress / m_progressTimer.GetCountdownDuration(), 0.9f );
+ }
+
+ return RemapValClamped( flTimeProgress, 0.f, 1.f, m_nStartXP, m_nTargetXP );
+}
+
+bool CPvPRankPanel::XPState_t::BeginXPDeltaLerp()
+{
+ if ( !m_bCurrentDeltaViewed )
+ {
+ m_bCurrentDeltaViewed = true;
+ // Change our target
+ m_nTargetXP = m_nActualXP;
+ m_progressTimer.Start( 5.f );
+
+ return true;
+ }
+
+ return false;
+}
+
+void CPvPRankPanel::XPState_t::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
+{
+ if ( pObject->GetTypeID() == CSOTFLadderData::k_nTypeID )
+ {
+ CSOTFLadderData* pLadderObject = (CSOTFLadderData*)pObject;
+ if( (EMatchGroup)pLadderObject->Obj().match_group() != m_eMatchGroup )
+ return;
+
+ // We'll get a eSOCacheEvent_Incremental when we're actually creating the
+ // first CSOTFLadderData object. We dont want that to come through as
+ // "initializing" because it'll skip the leveling effects
+ UpdateXP( eEvent == eSOCacheEvent_ListenerAdded );
+ }
+
+ if ( pObject->GetTypeID() == CXPSource::k_nTypeID )
+ {
+ CXPSource *pXPSource = (CXPSource*)pObject;
+ if ( pXPSource->Obj().match_group() == m_eMatchGroup )
+ {
+ UpdateXP( false );
+ }
+ }
+}
+
+void CPvPRankPanel::XPState_t::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
+{
+ if ( pObject->GetTypeID() == CSOTFLadderData::k_nTypeID )
+ {
+ CSOTFLadderData* pLadderObject = (CSOTFLadderData*)pObject;
+ if( (EMatchGroup)pLadderObject->Obj().match_group() != m_eMatchGroup )
+ return;
+
+ // If the GC comes on after we've already subscribed to the cache,
+ // eSOCacheEvent_Subscribed when the object is created. This one
+ // we want to skip the effects.
+ UpdateXP( eEvent == eSOCacheEvent_Subscribed );
+ }
+
+ if ( pObject->GetTypeID() == CXPSource::k_nTypeID )
+ {
+ CXPSource *pXPSource = (CXPSource*)pObject;
+ if ( pXPSource->Obj().match_group() == m_eMatchGroup )
+ {
+ UpdateXP( false );
+ }
+ }
+}
+
+class CMiniPvPRankPanel : public CPvPRankPanel
+{
+public:
+ CMiniPvPRankPanel( Panel* pParent, const char* pszPanelName ) : CPvPRankPanel( pParent, pszPanelName )
+ {}
+
+private:
+ virtual KeyValues* GetConditions() const OVERRIDE
+ {
+ KeyValues* pConditions = new KeyValues( "conditions" );
+ pConditions->AddSubKey( new KeyValues( "if_mini" ) );
+
+ return pConditions;
+ }
+};
+
+class CXPSourcePanel : public vgui::EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CXPSourcePanel , vgui::EditablePanel);
+ CXPSourcePanel( Panel *pParent, const char* panelName, const CMsgTFXPSource& source )
+ : BaseClass( pParent, panelName )
+ , m_source( source )
+ {}
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/XPSourcePanel.res" );
+ }
+
+ virtual void PerformLayout() OVERRIDE
+ {
+ BaseClass::PerformLayout();
+
+ static wchar_t wszOutString[ 128 ];
+ wchar_t wszCount[ 16 ];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_source.amount() );
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( g_XPSourceDefs[ m_source.type() ].m_pszFormattingLocToken );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, g_pVGuiLocalize->Find( g_XPSourceDefs[ m_source.type() ].m_pszTypeLocToken ), wszCount );
+
+ SetDialogVariable( "source", wszOutString );
+ }
+
+protected:
+ CMsgTFXPSource m_source;
+};
+
+class CScrollingXPSourcePanel : public CXPSourcePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CScrollingXPSourcePanel , CXPSourcePanel );
+ CScrollingXPSourcePanel( Panel *pParent, const char* panelName, const CMsgTFXPSource& source, float flStartTime )
+ : BaseClass( pParent, panelName, source )
+ , m_flStartTime( flStartTime )
+ , m_bStarted( false )
+ {
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ SetAutoDelete( false );
+ }
+
+ virtual ~CScrollingXPSourcePanel()
+ {
+ }
+
+ virtual void OnTick() OVERRIDE
+ {
+ BaseClass::OnTick();
+
+ if ( Plat_FloatTime() > m_flStartTime && !m_bStarted )
+ {
+ m_bStarted = true;
+ SetVisible( true );
+
+ // Do starting stuff
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_source.amount() >= 0 ? "XPSourceShow_Positive" : "XPSourceShow_Negative", false );
+
+ if ( g_XPSourceDefs[ m_source.type() ].m_pszSoundName )
+ {
+ PlaySoundEntry( g_XPSourceDefs[ m_source.type() ].m_pszSoundName );
+ }
+ }
+
+ SetVisible( m_bStarted );
+
+ if ( Plat_FloatTime() > ( m_flStartTime + tf_xp_breakdown_lifetime.GetFloat() ) )
+ {
+ // We're done! Delete ourselves
+ MarkForDeletion();
+ }
+ }
+
+private:
+
+ float m_flStartTime;
+ bool m_bStarted;
+};
+
+DECLARE_BUILD_FACTORY( CMiniPvPRankPanel );
+DECLARE_BUILD_FACTORY( CPvPRankPanel );
+
+CPvPRankPanel::CPvPRankPanel( Panel *parent, const char *panelName )
+ : BaseClass( parent, panelName )
+ , m_eMatchGroup( k_nMatchGroup_Invalid )
+ , m_pProgressionDesc( NULL )
+ , m_pContinuousProgressBar( NULL )
+ , m_pModelPanel( NULL )
+ , m_pXPBar( NULL )
+ , m_pBGPanel( NULL )
+ , m_nLastLerpXP( 0 )
+ , m_nLastSeenLevel( 0 )
+ , m_bClicked( false )
+{
+ ListenForGameEvent( "begin_xp_lerp" );
+}
+
+void CPvPRankPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ KeyValues* pConditions = GetConditions();
+
+ LoadControlSettings( GetResFile(), NULL, NULL, pConditions );
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ pConditions = NULL;
+ }
+
+ m_pBGPanel = FindControl< EditablePanel >( "BGPanel", true );
+ m_pContinuousProgressBar = FindControl< ContinuousProgressBar >( "ContinuousProgressBar", true );
+ m_pXPBar = FindControl< EditablePanel >( "XPBar", true );
+ m_pModelPanel = FindControl< CBaseModelPanel >( "RankModel", true );
+ m_pModelPanel->AddActionSignalTarget( this );
+
+ m_pModelButton = FindChildByName( "MedalButton", true );
+
+#ifdef STAGING_ONLY
+ if ( m_pBGPanel )
+ {
+ Panel* pDebugButton = m_pBGPanel->FindChildByName( "TestLevelDownButton", true );
+ if ( pDebugButton ) { pDebugButton->SetVisible( true ); }
+ pDebugButton = m_pBGPanel->FindChildByName( "LevelDebugButton", true );
+ if ( pDebugButton ) { pDebugButton->SetVisible( true ); }
+ pDebugButton = m_pBGPanel->FindChildByName( "TestLevelUpButton", true );
+ if ( pDebugButton ) { pDebugButton->SetVisible( true ); }
+ }
+#endif
+}
+
+void CPvPRankPanel::ApplySettings(KeyValues *inResourceData)
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ SetMatchGroup( (EMatchGroup)StringFieldToInt( inResourceData->GetString( "matchgroup" ), s_pszMatchGroups, (int)k_nMatchGroup_Count, false ) );
+}
+
+void CPvPRankPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( !m_pProgressionDesc )
+ return;
+
+ EditablePanel* pStatsContainer = FindControl< EditablePanel >( "Stats", true );
+ if ( !pStatsContainer )
+ return;
+
+ if ( !m_pModelPanel )
+ return;
+
+ if ( !m_pXPBar )
+ return;
+
+ ProgressBar* pProgressBar = FindControl< ProgressBar > ( "ProgressBar", true );
+ if ( !pProgressBar )
+ return;
+
+ if ( !m_pContinuousProgressBar )
+ return;
+
+ // Chop up the progress bar into 10th's and use it as a mask overtop
+ pProgressBar->SetSegmentInfo( ( pProgressBar->GetWide() / 10 ) - ( ( 9 * 4 ) / 10 ) , 4 );
+
+ const XPState_t& xpstate = GetXPState();
+ uint32 nCurrentXP = xpstate.GetCurrentXP();
+ const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP );
+ m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );
+
+ // Update labels
+ UpdateControls( xpstate.GetStartXP(), nCurrentXP, levelCur );
+
+ SetMatchStats();
+}
+
+void CPvPRankPanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( m_pContinuousProgressBar && m_pXPBar && m_pModelPanel && m_pProgressionDesc && m_pBGPanel )
+ {
+
+ // SUPER HACKS. I dont have time to figure out popups
+ if ( m_pModelButton && vgui::input()->IsMouseDown( MOUSE_LEFT ) )
+ {
+ int nMouseX, nMouseY;
+ input()->GetCursorPos( nMouseX, nMouseY );
+ if ( !m_bClicked && m_pModelButton->IsWithin( nMouseX, nMouseY ) )
+ {
+ OnCommand( "medal_clicked" );
+ }
+
+ m_bClicked = true;
+ }
+ else
+ {
+ m_bClicked = false;
+ }
+
+ const XPState_t& xpstate = GetXPState();
+ uint32 nCurrentXP = xpstate.GetCurrentXP();
+
+ // Check if the last XP we lerp'd to isn't the current XP. If it's not, then we want to animate over to it.
+ if ( m_nLastLerpXP != nCurrentXP )
+ {
+ uint32 nPrevXP = xpstate.GetStartXP();
+ const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP );
+
+ UpdateControls( nPrevXP, nCurrentXP, levelCur );
+
+ // We only want to do level up effects if we are thinking (visible) when the current XP passes the level boundary
+ if ( m_nLastLerpXP != xpstate.GetStartXP() )
+ {
+ const LevelInfo_t& levelPrevThink = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP );
+ if ( levelCur.m_nLevelNum > levelPrevThink.m_nLevelNum ) // Level up :)
+ {
+ PlayLevelUpEffects( levelCur );
+ }
+ else if ( levelCur.m_nLevelNum < levelPrevThink.m_nLevelNum ) // Level down :(
+ {
+ PlayLevelDownEffects( levelCur );
+ }
+ }
+
+ m_nLastLerpXP = nCurrentXP;
+ }
+ else
+ {
+ // Our last lerp XP is caught up with current XP, but our last seen level is not up to date with what
+ // our current level actually is. This can happen if we haven't been thinking, but we did level up somewhere
+ // else. In this case, we need to update our badge.
+ const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP );
+
+ if ( m_nLastSeenLevel != levelCur.m_nLevelNum )
+ {
+ m_nLastSeenLevel = levelCur.m_nLevelNum;
+ m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );
+ }
+ }
+ }
+}
+
+
+void CPvPRankPanel::UpdateControls( uint32 nPreviousXP, uint32 nCurrentXP, const LevelInfo_t& levelCurrent )
+{
+ if ( m_pContinuousProgressBar )
+ {
+ // Calculate progress bar percentages. PrevBarProgress is the bar that shows where you started.
+ // CurrentBarProgress is the bar that lerps from the start to your actual, current XP
+ float flPrevBarProgress = RemapValClamped( (float)nPreviousXP, (float)levelCurrent.m_nStartXP, (float)levelCurrent.m_nEndXP, 0.f, 1.f );
+ float flCurrentBarProgress = RemapValClamped( (float)nCurrentXP, (float)levelCurrent.m_nStartXP, (float)levelCurrent.m_nEndXP, 0.f, 1.f );
+
+ m_pContinuousProgressBar->SetPrevProgress( flPrevBarProgress );
+ m_pContinuousProgressBar->SetProgress( flCurrentBarProgress );
+ }
+
+ if ( m_pXPBar )
+ {
+ m_pXPBar->SetDialogVariable( "current_xp", LocalizeNumberWithToken( "TF_Competitive_XP_Current", nCurrentXP ) );
+
+ if ( levelCurrent.m_nLevelNum < m_pProgressionDesc->GetNumLevels() )
+ {
+ m_pXPBar->SetDialogVariable( "next_level_xp", LocalizeNumberWithToken( "TF_Competitive_XP", levelCurrent.m_nEndXP ) );
+ }
+ else // Hide the next level XP value at max level
+ {
+ m_pXPBar->SetDialogVariable( "next_level_xp", "" );
+ }
+
+ static wchar_t wszOutString[ 128 ];
+ wchar_t wszCount[ 16 ];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", levelCurrent.m_nLevelNum );
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( m_pProgressionDesc->m_pszLevelToken );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, wszCount, g_pVGuiLocalize->Find( levelCurrent.m_pszLevelTitle ) );
+
+ m_pXPBar->SetDialogVariable( "level", wszOutString );
+ }
+}
+
+void CPvPRankPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( "medal_clicked", command ) )
+ {
+ // Default effects
+ const char *pszSeqName = "click_A";
+ const char *pszSoundName = "ui/mm_medal_click.wav";
+
+ // Roll for a crit
+ int nRandomRoll = RandomInt( 0, 9 );
+ if ( nRandomRoll == 0 )
+ {
+ // CRIT!
+ pszSeqName = "click_B";
+ pszSoundName = "MatchMaking.MedalClickRare";
+ }
+
+ m_pModelPanel->PlaySequence( pszSeqName );
+ PlaySoundEntry( pszSoundName );
+
+ EditablePanel* pModelContainer = FindControl< EditablePanel >( "ModelContainer" );
+ if ( pModelContainer )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankModelClicked", false);
+ }
+
+ return;
+ }
+ else if ( FStrEq( "begin_xp_lerp", command ) )
+ {
+ BeginXPLerp();
+ return;
+ }
+ else if ( FStrEq( "update_base_state", command ) )
+ {
+ UpdateBaseState();
+ return;
+ }
+ BaseClass::OnCommand( command );
+}
+
+void CPvPRankPanel::FireGameEvent( IGameEvent *pEvent )
+{
+ // This is really only for tf_test_pvp_rank_xp_change
+ if ( FStrEq( pEvent->GetName(), "begin_xp_lerp" ) )
+ {
+ BeginXPLerp();
+ }
+}
+
+void CPvPRankPanel::SetVisible( bool bVisible )
+{
+ if ( IsVisible() != bVisible )
+ {
+ UpdateBaseState();
+ }
+
+ BaseClass::SetVisible( bVisible );
+}
+
+int SortXPSources( CXPSource* const* pLeft, CXPSource* const* pRight )
+{
+ return (*pLeft)->Obj().type() - (*pRight)->Obj().type();
+}
+
+void CPvPRankPanel::BeginXPLerp()
+{
+ XPState_t& xpstate = GetXPState();
+ if ( xpstate.BeginXPDeltaLerp() )
+ {
+ // Play sounds if this is a change
+ if ( xpstate.GetTargetXP() > xpstate.GetStartXP() )
+ {
+ PlaySoundEntry( "MatchMaking.RankProgressTickUp" );
+ }
+ else if ( xpstate.GetTargetXP() < xpstate.GetStartXP() )
+ {
+ PlaySoundEntry( "MatchMaking.RankProgressTickDown" );
+ }
+ }
+
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( steamapicontext->SteamUser()->GetSteamID() );
+
+ if ( pSOCache )
+ {
+ GCSDK::CGCClientSharedObjectTypeCache *pTypeCache = pSOCache->FindTypeCache( CXPSource::k_nTypeID );
+
+ if ( pTypeCache )
+ {
+ CUtlVector< CXPSource* > vecSources;
+
+ // Grab all the XP sources we want to show
+ for ( uint32 i = 0; i < pTypeCache->GetCount(); ++i )
+ {
+ CXPSource *pXPSource = (CXPSource*)pTypeCache->GetObject( i );
+
+ if ( pXPSource->Obj().match_group() == m_eMatchGroup )
+ {
+ vecSources.AddToTail( pXPSource );
+ }
+ }
+
+ // Sort them so users get a consistent experience
+ vecSources.Sort( &SortXPSources );
+
+ // Show the sources
+ FOR_EACH_VEC( vecSources, i )
+ {
+ CXPSource *pXPSource = vecSources[ i ];
+ CScrollingXPSourcePanel* pSourcePanel = new CScrollingXPSourcePanel( this, "XPSourcePanel", pXPSource->Obj(), Plat_FloatTime() + ( i * tf_xp_breakdown_interval.GetFloat() ) );
+ pSourcePanel->MakeReadyForUse();
+
+ // Offset from the BGPanel.
+ pSourcePanel->SetPos( m_pBGPanel->GetXPos() + m_iXPSourceNotificationCenterX - ( pSourcePanel->GetWide() * 0.5f ), m_pBGPanel->GetYPos() + m_pXPBar->GetYPos() + YRES( 22 ) - pSourcePanel->GetTall() );
+ }
+ }
+ }
+ }
+
+ GTFGCClientSystem()->AcknowledgePendingXPSources( m_eMatchGroup );
+}
+
+void CPvPRankPanel::UpdateBaseState()
+{
+ if ( !m_pProgressionDesc || m_pModelPanel == NULL )
+ return;
+
+ // Set our "last seen" variables so things dont try to interpolate
+ m_nLastLerpXP = GetXPState().GetCurrentXP();
+ LevelInfo_t levelCur = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP );
+ m_nLastSeenLevel = levelCur.m_nLevelNum;
+
+ m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );
+
+ // Update progress bars and labels
+ UpdateControls( GetXPState().GetStartXP(), m_nLastLerpXP, levelCur );
+}
+
+void CPvPRankPanel::OnAnimEvent( KeyValues *pParams )
+{
+ // This gets fired by the model panel that has the badge model in it when we want
+ // to do our bodygroup changes. This is so we can time it to when the model is doing
+ // a flashy maneuver to mask the bodygroup change pop
+ if ( FStrEq( pParams->GetString( "name" ), "AE_CL_BODYGROUP_SET_VALUE" ) && m_pProgressionDesc )
+ {
+ const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP );
+ m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );
+ }
+}
+
+void CPvPRankPanel::PlayLevelUpEffects( const LevelInfo_t& level ) const
+{
+ m_pModelPanel->PlaySequence( "level_up" );
+ PlaySoundEntry( level.m_pszLevelUpSound );
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pXPBar, "PvPRankLevelUpXPBar", false);
+
+ EditablePanel* pModelContainer = const_cast< CPvPRankPanel* >( this )->FindControl< EditablePanel >( "ModelContainer" );
+ if ( pModelContainer )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankLevelUpModel", false );
+ }
+}
+
+void CPvPRankPanel::PlayLevelDownEffects( const LevelInfo_t& level ) const
+{
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pXPBar, "PvPRankLevelDownXPBar", false);
+ m_pModelPanel->PlaySequence( "level_down" );
+
+ EditablePanel* pModelContainer = const_cast< CPvPRankPanel* >( this )->FindControl< EditablePanel >( "ModelContainer" );
+ if ( pModelContainer )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankLevelDownModel", false );
+ }
+}
+
+void CPvPRankPanel::SetMatchGroup( EMatchGroup eMatchGroup )
+{
+ if ( m_eMatchGroup != eMatchGroup )
+ {
+ m_eMatchGroup = eMatchGroup;
+ m_pProgressionDesc = NULL;
+
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup );
+ if ( pMatchDesc && pMatchDesc->m_pProgressionDesc )
+ {
+ // Snag the progression desc. We use it often
+ m_pProgressionDesc = pMatchDesc->m_pProgressionDesc;
+ UpdateBaseState();
+ }
+
+ Assert( m_pProgressionDesc );
+
+ // Many things needs to change
+ InvalidateLayout( true, true );
+ UpdateBaseState();
+ }
+}
+
+void CPvPRankPanel::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
+{
+ if ( pObject->GetTypeID() != CSOTFLadderData::k_nTypeID )
+ return;
+
+ SetMatchStats();
+}
+
+void CPvPRankPanel::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
+{
+ if ( pObject->GetTypeID() != CSOTFLadderData::k_nTypeID )
+ return;
+
+ SetMatchStats();
+}
+
+void CPvPRankPanel::SetMatchStats( void )
+{
+ EditablePanel* pStatsContainer = FindControl< EditablePanel >( "Stats", true );
+ if ( !pStatsContainer )
+ return;
+
+ CSOTFLadderData *pData = GetLocalPlayerLadderData( m_eMatchGroup );
+
+ // Update all stats. Default to 0 incase we dont have ladder data
+ int nGames = 0, nKills = 0, nDeaths = 0, nDamage = 0, nHealing = 0, nSupport = 0, nScore = 0;
+
+ if ( pData )
+ {
+ nGames = pData->Obj().games();
+ nKills = pData->Obj().kills();
+ nDeaths = pData->Obj().deaths();
+ nDamage = pData->Obj().damage();
+ nHealing = pData->Obj().healing();
+ nSupport = pData->Obj().support();
+ nScore = pData->Obj().score();
+ }
+
+ pStatsContainer->SetDialogVariable( "stat_games", LocalizeNumberWithToken( "TF_Competitive_Games", nGames ) );
+ pStatsContainer->SetDialogVariable( "stat_kills", LocalizeNumberWithToken( "TF_Competitive_Kills", nKills ) );
+ pStatsContainer->SetDialogVariable( "stat_deaths", LocalizeNumberWithToken( "TF_Competitive_Deaths", nDeaths ) );
+ pStatsContainer->SetDialogVariable( "stat_damage", LocalizeNumberWithToken( "TF_Competitive_Damage", nDamage ) );
+ pStatsContainer->SetDialogVariable( "stat_healing", LocalizeNumberWithToken( "TF_Competitive_Healing", nHealing ) );
+ pStatsContainer->SetDialogVariable( "stat_support", LocalizeNumberWithToken( "TF_Competitive_Support", nSupport ) );
+ pStatsContainer->SetDialogVariable( "stat_score", LocalizeNumberWithToken( "TF_Competitive_Score", nScore ) );
+}
+
+CPvPRankPanel::XPState_t& CPvPRankPanel::GetXPState() const
+{
+ // Singletons for each XPState_t
+ static CUtlMap< EMatchGroup, CPvPRankPanel::XPState_t* > s_mapXPStates( DefLessFunc( EMatchGroup ) );
+
+ auto idx = s_mapXPStates.Find( m_eMatchGroup );
+ if ( idx == s_mapXPStates.InvalidIndex() )
+ {
+ idx = s_mapXPStates.Insert( m_eMatchGroup, new CPvPRankPanel::XPState_t( m_eMatchGroup ) );
+ }
+
+ return *s_mapXPStates[ idx ];
+}
+
+const char* CPvPRankPanel::GetResFile() const
+{
+ return m_pProgressionDesc ? m_pProgressionDesc->m_pszProgressionResFile : "resource/ui/PvPCompRankPanel.res";
+}
+
+KeyValues* CPvPRankPanel::GetConditions() const
+{
+ return NULL;
+}
diff --git a/game/client/tf/vgui/tf_pvp_rank_panel.h b/game/client/tf/vgui/tf_pvp_rank_panel.h
new file mode 100644
index 0000000..1af4daf
--- /dev/null
+++ b/game/client/tf/vgui/tf_pvp_rank_panel.h
@@ -0,0 +1,114 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#ifndef TF_PVP_RANK_PANEL_H
+#define TF_PVP_RANK_PANEL_H
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "tf_match_description.h"
+#include "GameEventListener.h"
+#include "local_steam_shared_object_listener.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+class CBaseModelPanel;
+namespace vgui
+{
+ class ContinuousProgressBar;
+};
+
+namespace GCSDK
+{
+ class CSharedObject;
+};
+
+using namespace GCSDK;
+
+class CPvPRankPanel : public vgui::EditablePanel, public CLocalSteamSharedObjectListener, public CGameEventListener
+{
+public:
+ DECLARE_CLASS_SIMPLE( CPvPRankPanel, vgui::EditablePanel );
+
+ CPvPRankPanel( Panel *parent, const char *panelName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *pEvent ) OVERRIDE;
+ virtual void SetVisible( bool bVisible ) OVERRIDE;
+
+ void SetMatchGroup( EMatchGroup eMatchGroup );
+ void SetMatchStats( void );
+
+ virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE;
+ virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE;
+
+ MESSAGE_FUNC_PARAMS( OnAnimEvent, "AnimEvent", pParams );
+
+protected:
+
+ virtual void PlayLevelUpEffects( const LevelInfo_t& level ) const;
+ virtual void PlayLevelDownEffects( const LevelInfo_t& level ) const;
+
+private:
+
+ struct XPState_t : public CGameEventListener, public CLocalSteamSharedObjectListener
+ {
+ XPState_t( EMatchGroup eMatchGroup );
+
+ virtual void FireGameEvent( IGameEvent *pEvent ) OVERRIDE;
+ // Get the current XP value
+ uint32 GetCurrentXP() const;
+ // Get the previous XP value before any changes occurred
+ uint32 GetStartXP() const { return m_nStartXP; }
+ // Get the target XP that the delta lerp is headed to
+ uint32 GetTargetXP() const { return m_nTargetXP; }
+ uint32 GetActualXP() const { return m_nActualXP; }
+ // Start the timer that causes GetCurrentXP to lerp from m_nStartXP to m_nTargetXP
+ bool BeginXPDeltaLerp();
+
+ virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE;
+ virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) OVERRIDE;
+
+ private:
+
+ void UpdateXP( bool bInitial );
+
+ const EMatchGroup m_eMatchGroup;
+ uint32 m_nStartXP; // The XP we show to the user that was the last state they saw
+ uint32 m_nTargetXP; // The XP the user believes is their current value
+ uint32 m_nActualXP; // The actual latest XP value
+ RealTimeCountdownTimer m_progressTimer;
+ bool m_bCurrentDeltaViewed;
+ };
+
+ XPState_t& GetXPState() const;
+ void UpdateControls( uint32 nPreviousXP, uint32 nCurrentXP, const LevelInfo_t& levelCurrent );
+ void UpdateBaseState();
+
+ virtual const char* GetResFile() const;
+ virtual KeyValues* GetConditions() const;
+ void BeginXPLerp();
+
+ EMatchGroup m_eMatchGroup;
+ vgui::ContinuousProgressBar* m_pContinuousProgressBar;
+ vgui::EditablePanel* m_pXPBar;
+ CBaseModelPanel* m_pModelPanel;
+ const IProgressionDesc* m_pProgressionDesc;
+ EditablePanel* m_pBGPanel;
+ bool m_bClicked;
+ Panel* m_pModelButton;
+
+ uint32 m_nLastLerpXP;
+ uint32 m_nLastSeenLevel;
+
+ CPanelAnimationVarAliasType( int, m_iXPSourceNotificationCenterX, "xp_source_notification_center_x", "19", "proportional_int" );
+};
+
+#endif //TF_PVP_RANK_PANEL_H \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_roundinfo.cpp b/game/client/tf/vgui/tf_roundinfo.cpp
new file mode 100644
index 0000000..ccfba71
--- /dev/null
+++ b/game/client/tf/vgui/tf_roundinfo.cpp
@@ -0,0 +1,623 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include <vgui_controls/Label.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui_controls/RichText.h>
+#include <vgui_controls/Frame.h>
+#include <game/client/iviewport.h>
+#include <KeyValues.h>
+#include <filesystem.h>
+#include "materialsystem/imaterialvar.h"
+#include "IGameUIFuncs.h" // for key bindings
+
+#include "tf_controls.h"
+#include "tf_imagepanel.h"
+#include "c_team_objectiveresource.h"
+#include "c_tf_objective_resource.h"
+#include "c_tf_player.h"
+
+#include "tf_shareddefs.h"
+#include "tf_roundinfo.h"
+
+
+#include "vgui/ISurface.h"
+#include <vgui/ILocalize.h>
+#include <vgui/IVGui.h>
+#include "engine/IEngineSound.h"
+
+using namespace vgui;
+
+const char *GetMapDisplayName( const char *mapName );
+
+class RoundInfoOverlay : public vgui::EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( RoundInfoOverlay, vgui::EditablePanel );
+
+ RoundInfoOverlay( Panel *parent, const char *panelName ) : EditablePanel( parent, panelName )
+ {
+ m_iMode = 0;
+ m_flModeChangeTime = -1;
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
+
+ m_iBlueTeamTexture = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile(m_iBlueTeamTexture, "overviews/blueteam", true, false);
+
+ m_iRedTeamTexture = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile(m_iRedTeamTexture, "overviews/redteam", true, false);
+
+ m_iCapArrowTexture = vgui::surface()->CreateNewTextureID();
+ vgui::surface()->DrawSetTextureFile(m_iCapArrowTexture, "overviews/caparrows", true, false);
+
+ m_iFoundPoints = 0;
+ m_iNextRoundPoints[0] = -1;
+ m_iNextRoundPoints[1] = -1;
+
+ m_iLastCappedPoint = -1;
+ }
+
+ virtual ~RoundInfoOverlay( void )
+ {
+ if ( vgui::surface() )
+ {
+ if ( m_iBlueTeamTexture != -1 )
+ {
+ vgui::surface()->DestroyTextureID( m_iBlueTeamTexture );
+ m_iBlueTeamTexture = -1;
+ }
+
+ if ( m_iRedTeamTexture != -1 )
+ {
+ vgui::surface()->DestroyTextureID( m_iRedTeamTexture );
+ m_iRedTeamTexture = -1;
+ }
+
+ if ( m_iCapArrowTexture != -1 )
+ {
+ vgui::surface()->DestroyTextureID( m_iCapArrowTexture );
+ m_iCapArrowTexture = -1;
+ }
+ }
+ }
+
+ void Update( const char *szMapName );
+
+ virtual void Paint();
+
+ void SetState( int iPrevState, int iCurrentState, int iNextBattles );
+
+ virtual void OnTick( void );
+
+ void DrawTeamIcon( int x, int y, bool bBlueTeam, float flBloat = 1.0f );
+ void DrawCapArrows( int x0, int y0, int x1, int y1 );
+
+private:
+
+ // structure to hold a single control point
+ typedef struct
+ {
+ char m_szName[64];
+ int m_iXPos;
+ int m_iYPos;
+ bool m_bHideIcon;
+ } roundinfo_control_point_t;
+
+ CUtlVector < roundinfo_control_point_t > m_ControlPoints;
+
+ int m_iPrevState;
+ int m_iCurrentState;
+ int m_iMiniRoundMask;
+
+ // Time when we should change the text to the attack directive
+ int m_iMode; // 0 - start, 1 - previous round victory anim, 2 - attack directive, new round
+ float m_flModeChangeTime;
+
+ int m_iBlueTeamTexture;
+ int m_iRedTeamTexture;
+ int m_iCapArrowTexture;
+
+ int m_iLastCappedPoint;
+
+ int m_iFoundPoints;
+ int m_iNextRoundPoints[2];
+};
+
+DECLARE_BUILD_FACTORY( RoundInfoOverlay );
+
+void RoundInfoOverlay::Paint( void )
+{
+ BaseClass::Paint();
+
+ if ( m_ControlPoints.Count() <= 0 )
+ {
+ return;
+ }
+
+ // Draw the Cap Icons
+
+ for ( int i=0; i<m_ControlPoints.Count(); i++ )
+ {
+ if ( m_ControlPoints[i].m_bHideIcon )
+ continue;
+
+ int x = m_ControlPoints[i].m_iXPos;
+ int y = m_ControlPoints[i].m_iYPos;
+
+ switch( m_iMode )
+ {
+ case 0: // Show previous state
+ {
+ if ( i != m_iLastCappedPoint )
+ {
+ bool bBlueTeam = ( m_iPrevState & (1<<i) );
+ DrawTeamIcon( x, y, bBlueTeam );
+ }
+ }
+ break;
+
+ case 1: // Animate the point being capped
+ {
+ bool bWasBlueTeam = ( m_iPrevState & (1<<i) );
+
+ if ( i == m_iLastCappedPoint )
+ {
+ float flTimeUntilChange = m_flModeChangeTime - gpGlobals->curtime;
+
+ if ( flTimeUntilChange < 0.4f )
+ {
+ float flBloat = RemapVal( flTimeUntilChange, 0.0f, 0.4f, 1.0f, 2.5f );
+
+ DrawTeamIcon( x, y, !bWasBlueTeam, flBloat );
+ }
+ }
+ else
+ {
+ DrawTeamIcon( x, y, bWasBlueTeam );
+ }
+ }
+ break;
+
+ case 2: // Draw the current state and the next battle arrows
+ {
+ bool bPointInContention = (m_iNextRoundPoints[0] == i || m_iNextRoundPoints[1] == i );
+
+ bool bBlueTeam = ( m_iCurrentState & (1<<i) );
+ DrawTeamIcon( x, y, bBlueTeam, bPointInContention ? 1.4 : 1.0 ); // rescale? pop looks weird
+ }
+ break;
+ }
+ }
+
+ if ( m_iMode == 2 )
+ {
+ if ( m_iFoundPoints == 2 )
+ {
+ if ( ( m_flModeChangeTime - gpGlobals->curtime ) < 3.5f )
+ {
+ DrawCapArrows( m_ControlPoints[m_iNextRoundPoints[0]].m_iXPos,
+ m_ControlPoints[m_iNextRoundPoints[0]].m_iYPos,
+ m_ControlPoints[m_iNextRoundPoints[1]].m_iXPos,
+ m_ControlPoints[m_iNextRoundPoints[1]].m_iYPos );
+ }
+ }
+ }
+}
+
+void RoundInfoOverlay::DrawCapArrows( int x0, int y0, int x1, int y1 )
+{
+ vgui::surface()->DrawSetColor( Color(255,255,255,255) );
+
+ vgui::surface()->DrawSetTexture( m_iCapArrowTexture );
+
+ Vector2D a( x0, y0 );
+ Vector2D b( x1, y1 );
+
+ Vector2D dir = b - a;
+
+ Vector2D perp( -dir.y, dir.x );
+ perp.NormalizeInPlace();
+ perp *= YRES(50);
+
+ float bloat = sin(4*gpGlobals->curtime) * 0.1f;
+
+ Vector2D edgepoint = a + dir * 0.25f;
+ Vector2D edgepoint2 = b - dir * 0.25f;
+
+ edgepoint -= 0.25f * dir * bloat;
+ edgepoint2 += 0.25f * dir * bloat;
+
+ float uv1 = 0.0f, uv2 = 1.0f;
+ Vector2D uv12( uv1, uv2 );
+ Vector2D uv11( uv1, uv1 );
+ Vector2D uv21( uv2, uv1 );
+ Vector2D uv22( uv2, uv2 );
+
+ vgui::Vertex_t verts[4];
+ verts[0].Init( edgepoint - perp * 0.5f, uv12 );
+ verts[1].Init( edgepoint2 - perp * 0.5f, uv11 );
+ verts[2].Init( edgepoint2 + perp * 0.5f, uv21 );
+ verts[3].Init( edgepoint + perp * 0.5f, uv22 );
+
+ vgui::surface()->DrawTexturedPolygon( 4, verts );
+}
+
+void RoundInfoOverlay::DrawTeamIcon( int x, int y, bool bBlueTeam, float flBloat /* = 1.0f */ )
+{
+ float flWide = YRES(45) * flBloat;
+
+ int xpos = x - flWide * 0.5f;
+ int ypos = y - flWide * 0.5f;
+
+ vgui::surface()->DrawSetColor( Color(255,255,255,255) );
+ vgui::surface()->DrawSetTexture( bBlueTeam ? m_iBlueTeamTexture : m_iRedTeamTexture );
+ vgui::surface()->DrawTexturedRect( xpos, ypos, xpos + flWide, ypos + flWide );
+}
+
+void RoundInfoOverlay::Update( const char *szMapName )
+{
+ KeyValues *kvCapPoints = NULL;
+
+ char strFullpath[MAX_PATH];
+ Q_strncpy( strFullpath, "resource/roundinfo/", MAX_PATH ); // Assume we must play out of the media directory
+ Q_strncat( strFullpath, szMapName, MAX_PATH );
+
+#ifdef _X360
+ char *pExt = Q_stristr( strFullpath, ".360" );
+ if ( pExt )
+ {
+ *pExt = '\0';
+ }
+#endif
+
+ Q_strncat( strFullpath, ".res", MAX_PATH ); // Assume we're a .res extension type
+
+ if ( g_pFullFileSystem->FileExists( strFullpath ), "MOD" )
+ {
+ kvCapPoints = new KeyValues( strFullpath );
+
+ if ( kvCapPoints )
+ {
+ if ( kvCapPoints->LoadFromFile( g_pFullFileSystem, strFullpath ) )
+ {
+ m_ControlPoints.RemoveAll();
+
+ for ( KeyValues *pData = kvCapPoints->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
+ {
+ roundinfo_control_point_t point;
+
+ Q_snprintf( point.m_szName, sizeof(point.m_szName), "%s", pData->GetName() );
+
+ // These x,y coords are relative to a 640x480 parent panel.
+ int wide, tall;
+ GetSize( wide, tall );
+
+ // can't use XRES, YRES because of widescreen
+ point.m_iXPos = (int)( (float)pData->GetInt( "x", 0 ) * ( ( float )wide / 560.0f ) );
+ point.m_iYPos = (int)( (float)pData->GetInt( "y", 0 ) * ( ( float )tall / 280.0f ) );
+
+ point.m_bHideIcon = ( pData->GetInt( "hideicon", 0 ) > 0 );
+
+ m_ControlPoints.AddToTail( point );
+ }
+ }
+
+ kvCapPoints->deleteThis();
+ }
+ }
+}
+
+void RoundInfoOverlay::SetState( int iPrevState, int iCurrentState, int iNextBattles )
+{
+ m_iPrevState = iPrevState;
+ m_iCurrentState = iCurrentState;
+ m_iMiniRoundMask = iNextBattles;
+
+ m_iMode = 0;
+ m_flModeChangeTime = gpGlobals->curtime + 0.5f;
+
+ // Find the two points that are being fought over
+
+ m_iFoundPoints = 0;
+
+ for ( int i=0;i<8 && m_iFoundPoints<2;i++ )
+ {
+ if ( m_iMiniRoundMask & (1<<i) )
+ {
+ m_iNextRoundPoints[m_iFoundPoints] = i;
+ m_iFoundPoints++;
+ }
+ }
+
+ // Make sure the blue point is in m_iNextRoundPoints[0]
+ if ( m_iFoundPoints >= 2 )
+ {
+ if ( !( m_iCurrentState & (1<<m_iNextRoundPoints[0]) ) )
+ {
+ // The first point is red! swap them
+ int temp = m_iNextRoundPoints[0];
+ m_iNextRoundPoints[0] = m_iNextRoundPoints[1];
+ m_iNextRoundPoints[1] = temp;
+ }
+ }
+
+ m_iLastCappedPoint = -1;
+
+ // Find the index of the point that was just capped
+ int iMaskedCappedPoint = m_iCurrentState ^ m_iPrevState;
+
+ if ( iMaskedCappedPoint != 0 )
+ {
+ int iIndex = 0;
+
+ // Find the index of the point that changed
+ while ( !( iMaskedCappedPoint & 0x1 ) )
+ {
+ iMaskedCappedPoint = iMaskedCappedPoint>>1;
+ iIndex++;
+ }
+ m_iLastCappedPoint = iIndex;
+ }
+}
+
+
+ConVar tf_roundinfo_pause( "tf_roundinfo_pause", "0", FCVAR_DEVELOPMENTONLY );
+
+void RoundInfoOverlay::OnTick( void )
+{
+ // Stop ticking when our parent is invisible
+ Panel *parent = GetParent();
+ if ( m_iMode >= 0 && ( !parent || !parent->IsVisible() ) )
+ {
+ m_iMode = -1;
+ return;
+ }
+
+ BaseClass::OnTick();
+
+ if ( tf_roundinfo_pause.GetBool() == false && m_flModeChangeTime <= gpGlobals->curtime )
+ {
+ switch( m_iMode )
+ {
+ case 0:
+ {
+ // start showing previous round anim
+ if ( m_iCurrentState != m_iPrevState )
+ {
+ m_iMode = 1;
+ m_flModeChangeTime = gpGlobals->curtime + 1.5f;
+ }
+ else
+ {
+ m_iMode = 2;
+ m_flModeChangeTime = gpGlobals->curtime + 4.0f;
+ }
+ }
+ break;
+
+ case 1:
+ {
+ // start showing next round plan
+ m_iMode = 2;
+ m_flModeChangeTime = gpGlobals->curtime + 4.0f;
+
+ CLocalPlayerFilter filter;
+ C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, "Hud.EndRoundScored" );
+ }
+ break;
+
+ case 2:
+ {
+ // we're done, hide the panel
+ //GetParent()->OnCommand( "continue" );
+ //m_iMode = -1;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFRoundInfo::CTFRoundInfo( IViewPort *pViewPort ) : Frame( NULL, PANEL_ROUNDINFO )
+{
+ m_pViewPort = pViewPort;
+
+ // load the new scheme early!!
+ SetScheme( "ClientScheme" );
+
+ SetTitleBarVisible( false );
+ SetMinimizeButtonVisible( false );
+ SetMaximizeButtonVisible( false );
+ SetCloseButtonVisible( false );
+ SetSizeable( false );
+ SetMoveable( false );
+ SetProportional( true );
+ SetVisible( false );
+ SetKeyBoardInputEnabled( true );
+
+ m_pTitle = new CExLabel( this, "RoundTitle", " " );
+ m_pMapImage = new ImagePanel( this, "MapImage" );
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+#else
+ m_pContinue = new CExButton( this, "RoundContinue", "#TF_Continue" );
+#endif
+
+ m_pOverlay = new RoundInfoOverlay( this, "Overlay" );
+
+ ListenForGameEvent( "game_newmap" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::PerformLayout()
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/RoundInfo.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ Update();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::ShowPanel( bool bShow )
+{
+ if ( IsVisible() == bShow )
+ return;
+
+ if ( bShow )
+ {
+ // look for the textures we want to use and don't show the roundinfo panel if any are missing
+ char temp[255];
+ Q_snprintf( temp, sizeof( temp ), "VGUI/%s", m_szMapImage );
+ IMaterial *pMapMaterial = materials->FindMaterial( temp, TEXTURE_GROUP_VGUI, false );
+
+ // are we missing any of the images we want to show?
+ if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) )
+ {
+ Activate();
+ }
+ else
+ {
+ SetVisible( false );
+ }
+ }
+ else
+ {
+ SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::OnCommand( const char *command )
+{
+ if ( !Q_strcmp( command, "continue" ) )
+ {
+ m_pViewPort->ShowPanel( this, false );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::UpdateImage( ImagePanel *pImagePanel, const char *pszImageName )
+{
+ if ( pImagePanel && ( Q_strlen( pszImageName ) > 0 ) )
+ {
+ char szTemp[255];
+ Q_snprintf( szTemp, sizeof( szTemp ), "VGUI/%s", pszImageName );
+
+ IMaterial *pTemp = materials->FindMaterial( szTemp, TEXTURE_GROUP_VGUI, false );
+ if ( pTemp && !IsErrorMaterial( pTemp ) )
+ {
+ pImagePanel->SetImage( pszImageName );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::Update()
+{
+ char szMapName[MAX_MAP_NAME];
+ Q_FileBase( engine->GetLevelName(), szMapName, sizeof(szMapName) );
+ Q_strlower( szMapName );
+
+ SetDialogVariable( "mapname", GetMapDisplayName( szMapName ) );
+
+ if ( m_pMapImage )
+ {
+ char temp[255];
+ Q_snprintf( temp, sizeof(temp), "../overviews/%s", szMapName );
+ Q_strncpy( m_szMapImage, temp, sizeof( m_szMapImage ) );
+
+ UpdateImage( m_pMapImage, m_szMapImage );
+ }
+
+ if ( m_pOverlay )
+ {
+ m_pOverlay->Update( szMapName );
+ }
+
+#ifndef _X360
+ if ( m_pContinue )
+ {
+ m_pContinue->RequestFocus();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::OnKeyCodePressed( KeyCode code )
+{
+ if( code == KEY_SPACE ||
+ code == KEY_ENTER ||
+ code == KEY_XBUTTON_A ||
+ code == KEY_XBUTTON_B ||
+ code == STEAMCONTROLLER_A ||
+ code == STEAMCONTROLLER_B )
+ {
+ OnCommand( "continue" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::SetData( KeyValues *data )
+{
+ if ( m_pOverlay )
+ {
+ m_pOverlay->SetState( data->GetInt( "prev" ), data->GetInt( "cur" ), data->GetInt( "round" ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRoundInfo::FireGameEvent( IGameEvent *event )
+{
+ if ( Q_strcmp( event->GetName(), "game_newmap" ) == 0 )
+ {
+ Update();
+ }
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_roundinfo.h b/game/client/tf/vgui/tf_roundinfo.h
new file mode 100644
index 0000000..6d30e23
--- /dev/null
+++ b/game/client/tf/vgui/tf_roundinfo.h
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_ROUNDINFO_H
+#define TF_ROUNDINFO_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: displays the RoundInfo menu
+//-----------------------------------------------------------------------------
+class RoundInfoOverlay;
+
+class CTFRoundInfo : public vgui::Frame, public IViewPortPanel, public CGameEventListener
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFRoundInfo, vgui::Frame );
+
+public:
+ CTFRoundInfo( IViewPort *pViewPort );
+
+ virtual const char *GetName( void ){ return PANEL_ROUNDINFO; }
+ virtual void SetData( KeyValues *data );
+ virtual void Reset(){ Update(); }
+ virtual void Update();
+ virtual bool NeedsUpdate( void ){ return false; }
+ virtual bool HasInputElements( void ){ return true; }
+ virtual void ShowPanel( bool bShow );
+
+ // both vgui::Frame and IViewPortPanel define these, so explicitly define them here as passthroughs to vgui
+ vgui::VPANEL GetVPanel( void ){ return BaseClass::GetVPanel(); }
+ virtual bool IsVisible(){ return BaseClass::IsVisible(); }
+ virtual void SetParent( vgui::VPANEL parent ){ BaseClass::SetParent( parent ); }
+
+ virtual void FireGameEvent( IGameEvent *event );
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; }
+
+protected:
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+ virtual void PerformLayout();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+
+ void UpdateImage( vgui::ImagePanel *pImagePanel, const char *pszImageName );
+
+protected:
+ IViewPort *m_pViewPort;
+
+ CExLabel *m_pTitle;
+ vgui::ImagePanel *m_pMapImage;
+
+#ifdef _X360
+ CTFFooter *m_pFooter;
+#else
+ CExButton *m_pContinue;
+#endif
+
+ char m_szMapImage[MAX_ROUND_IMAGE_NAME];
+
+ RoundInfoOverlay *m_pOverlay;
+
+ int m_iFoundPoints;
+ int m_iNextRoundPoints[2];
+};
+
+
+#endif // TF_ROUNDINFO_H
diff --git a/game/client/tf/vgui/tf_spectatorgui.cpp b/game/client/tf/vgui/tf_spectatorgui.cpp
new file mode 100644
index 0000000..019944c
--- /dev/null
+++ b/game/client/tf/vgui/tf_spectatorgui.cpp
@@ -0,0 +1,1279 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hud.h"
+#include "c_team.h"
+#include "tf_playerpanel.h"
+#include "tf_spectatorgui.h"
+#include "tf_shareddefs.h"
+#include "tf_gamerules.h"
+#include "tf_hud_objectivestatus.h"
+#include "tf_hud_statpanel.h"
+#include "iclientmode.h"
+#include "c_playerresource.h"
+#include "tf_hud_building_status.h"
+#include "tf_hud_tournament.h"
+#include "tf_hud_winpanel.h"
+#include "tf_tips.h"
+#include "tf_mapinfomenu.h"
+#include "econ_wearable.h"
+#include "c_tf_playerresource.h"
+#include "playerspawncache.h"
+#include "econ_notifications.h"
+#include "tf_hud_item_progress_tracker.h"
+#include "tf_hud_target_id.h"
+#include "c_baseobject.h"
+#include "inputsystem/iinputsystem.h"
+
+#if defined( REPLAY_ENABLED )
+#include "replay/replay.h"
+#include "replay/ireplaysystem.h"
+#include "replay/ireplaymanager.h"
+#endif // REPLAY_ENABLED
+
+#include <vgui/ILocalize.h>
+#include <vgui/ISurface.h>
+#include "vgui_avatarimage.h"
+
+using namespace vgui;
+
+extern ConVar _cl_classmenuopen;
+extern ConVar tf_max_health_boost;
+extern const char *g_pszItemClassImages[];
+extern int g_ClassDefinesRemap[];
+extern ConVar tf_mvm_buybacks_method;
+
+const char *GetMapDisplayName( const char *mapName );
+
+static const wchar_t* GetSCGlyph( const char* action )
+{
+ auto origin = g_pInputSystem->GetSteamControllerActionOrigin( action, GAME_ACTION_SET_SPECTATOR );
+ return g_pInputSystem->GetSteamControllerFontCharacterForActionOrigin( origin );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFSpectatorGUI *GetTFSpectatorGUI()
+{
+ extern CSpectatorGUI *g_pSpectatorGUI;
+ return dynamic_cast< CTFSpectatorGUI * >( g_pSpectatorGUI );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ConVar cl_spec_carrieditems( "cl_spec_carrieditems", "1", FCVAR_ARCHIVE, "Show non-standard items being carried by player you're spectating." );
+
+void HUDTournamentSpecChangedCallBack( IConVar *var, const char *pOldString, float flOldValue )
+{
+ CTFSpectatorGUI *pPanel = (CTFSpectatorGUI*)gViewPortInterface->FindPanelByName( PANEL_SPECGUI );
+ if ( pPanel )
+ {
+ pPanel->InvalidateLayout( true, true );
+ }
+}
+ConVar cl_use_tournament_specgui( "cl_use_tournament_specgui", "0", FCVAR_ARCHIVE, "When in tournament mode, use the advanced tournament spectator UI.", HUDTournamentSpecChangedCallBack );
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFSpectatorGUI::CTFSpectatorGUI(IViewPort *pViewPort) : CSpectatorGUI(pViewPort)
+{
+ m_flNextTipChangeTime = 0;
+ m_iTipClass = TF_CLASS_UNDEFINED;
+
+ m_nEngBuilds_xpos = m_nEngBuilds_ypos = 0;
+ m_nSpyBuilds_xpos = m_nSpyBuilds_ypos = 0;
+
+ m_nMannVsMachineStatus_xpos = m_nMannVsMachineStatus_ypos = 0;
+ m_pBuyBackLabel = new CExLabel( this, "BuyBackLabel", "" );
+ m_pReinforcementsLabel = new Label( this, "ReinforcementsLabel", "" );
+ m_pClassOrTeamLabel = new Label( this, "ClassOrTeamLabel", "" );
+ // m_pSwitchCamModeKeyLabel = new Label( this, "SwitchCamModeKeyLabel", "" );
+ m_pSwitchCamModeKeyLabel = nullptr;
+ m_pClassOrTeamKeyLabel = nullptr;
+ m_pCycleTargetFwdKeyLabel = new Label( this, "CycleTargetFwdKeyLabel", "" );
+ m_pCycleTargetRevKeyLabel = new Label( this, "CycleTargetRevKeyLabel", "" );
+ m_pMapLabel = new Label( this, "MapLabel", "" );
+ m_pItemPanel = new CItemModelPanel( this, "itempanel" );
+
+ m_pStudentHealth = new CTFSpectatorGUIHealth( this, "StudentGUIHealth" );
+ m_pAvatar = NULL;
+
+ m_flNextItemPanelUpdate = 0;
+ m_flNextPlayerPanelUpdate = 0;
+ m_iPrevItemShown = 0;
+ m_iFirstItemShown = 0;
+ m_bShownItems = false;
+ m_hPrevItemPlayer = NULL;
+ m_pPlayerPanelKVs = NULL;
+ m_bReapplyPlayerPanelKVs = false;
+ m_bCoaching = false;
+
+ ListenForGameEvent( "spec_target_updated" );
+ ListenForGameEvent( "player_death" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFSpectatorGUI::~CTFSpectatorGUI()
+{
+ if ( m_pPlayerPanelKVs )
+ {
+ m_pPlayerPanelKVs->deleteThis();
+ m_pPlayerPanelKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::Reset( void )
+{
+ BaseClass::Reset();
+
+ for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
+ {
+ m_PlayerPanels[i]->Reset();
+ }
+
+ m_pStudentHealth->Reset();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFSpectatorGUI::GetTopBarHeight()
+{
+ int iPlayerPanelHeight = 0;
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( GetLocalPlayerTeam() == TF_TEAM_PVE_DEFENDERS )
+ {
+ if ( m_PlayerPanels.Count() > 0 )
+ {
+ iPlayerPanelHeight = m_PlayerPanels[0]->GetTall();
+ }
+ }
+ }
+
+ return m_pTopBar->GetTall() + iPlayerPanelHeight;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: makes the GUI fill the screen
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ if ( m_bReapplyPlayerPanelKVs )
+ {
+ m_bReapplyPlayerPanelKVs = false;
+
+ if ( m_pPlayerPanelKVs )
+ {
+ for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
+ {
+ m_PlayerPanels[i]->ApplySettings( m_pPlayerPanelKVs );
+ m_PlayerPanels[i]->InvalidateLayout( false, true );
+ }
+ }
+ }
+
+ if ( m_pStudentHealth )
+ {
+ Panel* pHealthPosPanel = FindChildByName( "HealthPositioning" );
+ if ( pHealthPosPanel )
+ {
+ int xPos, yPos, iWide, iTall;
+ pHealthPosPanel->GetBounds( xPos, yPos, iWide, iTall );
+ m_pStudentHealth->SetBounds( xPos, yPos, iWide, iTall );
+ m_pStudentHealth->SetZPos( pHealthPosPanel->GetZPos() );
+ }
+ }
+
+ UpdatePlayerPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ bool bVisible = IsVisible();
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_bReapplyPlayerPanelKVs = true;
+ m_bPrevTournamentMode = InTournamentGUI();
+
+ m_pSwitchCamModeKeyLabel = dynamic_cast< Label* >( FindChildByName( "SwitchCamModeKeyLabel" ) );
+
+ m_pAvatar = dynamic_cast<CAvatarImagePanel *>( FindChildByName("AvatarImage") );
+ if ( ::input->IsSteamControllerActive() )
+ {
+ m_pClassOrTeamKeyLabel = dynamic_cast< CExLabel* >( FindChildByName( "ClassOrTeamKeyLabel" ) );
+ }
+ else
+ {
+ m_pClassOrTeamKeyLabel = nullptr;
+ }
+
+ if ( m_bCoaching )
+ {
+ if ( m_pTopBar )
+ {
+ m_pTopBar->SetBgColor( Color( 255, 255, 255, 0 ) );
+ }
+ if ( m_pBottomBarBlank )
+ {
+ m_pBottomBarBlank->SetVisible( false );
+ }
+ }
+
+ if ( m_bCoaching )
+ {
+ if ( m_pClassOrTeamLabel && m_pClassOrTeamLabel->IsVisible() )
+ {
+ m_pClassOrTeamLabel->SetVisible( false );
+ }
+
+ if ( m_pClassOrTeamKeyLabel && m_pClassOrTeamKeyLabel->IsVisible() )
+ {
+ m_pClassOrTeamKeyLabel->SetVisible( false );
+ }
+ }
+
+ // Stay the same visibility as before the scheme reload.
+ SetVisible( bVisible );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "playerpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pPlayerPanelKVs )
+ {
+ m_pPlayerPanelKVs->deleteThis();
+ }
+ m_pPlayerPanelKVs = new KeyValues("playerpanels_kv");
+ pItemKV->CopySubkeys( m_pPlayerPanelKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFSpectatorGUI::NeedsUpdate( void )
+{
+ if ( !C_BasePlayer::GetLocalPlayer() )
+ return false;
+
+ if( IsVisible() )
+ return true;
+
+ return BaseClass::NeedsUpdate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::Update()
+{
+ BaseClass::Update();
+
+ UpdateReinforcements();
+ UpdateKeyLabels();
+
+ if ( m_flNextItemPanelUpdate < gpGlobals->curtime )
+ {
+ UpdateItemPanel();
+ }
+
+ // If we need to flip tournament mode, do it now
+ if ( m_bPrevTournamentMode != InTournamentGUI() )
+ {
+ InvalidateLayout( false, true );
+ }
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer && m_bCoaching != pLocalPlayer->m_bIsCoaching )
+ {
+ m_bCoaching = pLocalPlayer->m_bIsCoaching;
+ InvalidateLayout( false, true );
+ }
+ if ( pLocalPlayer && pLocalPlayer->m_hStudent && m_bCoaching )
+ {
+ Vector vecTarget = pLocalPlayer->m_hStudent->GetAbsOrigin();
+ Vector vecDelta = pLocalPlayer->GetAbsOrigin() - vecTarget;
+ float flDistance = vecDelta.Length();
+ const float kInchesToMeters = 0.0254f;
+ int distance = RoundFloatToInt( flDistance * kInchesToMeters );
+ wchar_t wzValue[32];
+ _snwprintf( wzValue, ARRAYSIZE( wzValue ), L"%u", distance );
+ wchar_t wzText[256];
+ g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( "#TR_DistanceToStudent" ), 1, wzValue );
+ SetDialogVariable( "student_distance", wzText );
+ }
+
+ if ( m_flNextPlayerPanelUpdate < gpGlobals->curtime )
+ {
+ RecalculatePlayerPanels();
+ m_flNextPlayerPanelUpdate = gpGlobals->curtime + 0.1f;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::UpdateReinforcements( void )
+{
+ if( !m_pReinforcementsLabel )
+ return;
+
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pPlayer || pPlayer->IsHLTV() ||
+ ( pPlayer->GetTeamNumber() != TF_TEAM_RED && pPlayer->GetTeamNumber() != TF_TEAM_BLUE ) ||
+ ( pPlayer->m_Shared.GetState() != TF_STATE_OBSERVER && pPlayer->m_Shared.GetState() != TF_STATE_DYING ) ||
+ ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) )
+ {
+ m_pReinforcementsLabel->SetVisible( false );
+ m_pBuyBackLabel->SetVisible( false );
+
+ return;
+ }
+
+ bool bBuyBackVisible = false;
+ wchar_t wLabel[256];
+
+ if ( TFGameRules()->InStalemate() )
+ {
+ if ( TFGameRules()->IsInArenaMode() == true )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Arena_NoRespawning" ), 0 );
+ }
+ else
+ {
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_stalemate" ), 0 );
+ }
+ }
+ else if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN )
+ {
+ // a team has won the round
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_next_round" ), 0 );
+ }
+ else
+ {
+ float flNextRespawn = 0.f;
+ bool bQuickSpawn = TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->IsPlayerClass( TF_CLASS_SCOUT );
+
+ if ( g_TF_PR )
+ {
+ flNextRespawn = g_TF_PR->GetNextRespawnTime( pPlayer->entindex() );
+ }
+ else if ( !bQuickSpawn )
+ {
+ flNextRespawn = TFGameRules()->GetNextRespawnWave( pPlayer->GetTeamNumber(), pPlayer );
+ }
+
+ if ( !flNextRespawn )
+ {
+ m_pReinforcementsLabel->SetVisible( false );
+ m_pBuyBackLabel->SetVisible( false );
+ return;
+ }
+
+ int iRespawnWait = (flNextRespawn - gpGlobals->curtime);
+ if ( iRespawnWait <= 0 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_now" ), 0 );
+ }
+ else if ( iRespawnWait <= 1.0 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_sec" ), 0 );
+ }
+ else
+ {
+ char szSecs[6];
+ wchar_t wSecs[4];
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ bool bNewMethod = tf_mvm_buybacks_method.GetBool();
+ bBuyBackVisible = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : true;
+
+ if ( bBuyBackVisible )
+ {
+ // When using the new system, we display "Hit '%use_action_slot_item%' to RESPAWN INSTANTLY! (%s1 remaining this wave)"
+ // When using the old system, we display "Hit '%use_action_slot_item%' to pay %s1 credits and RESPAWN INSTANTLY!"
+ int nCost = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : iRespawnWait * MVM_BUYBACK_COST_PER_SEC;
+ const char *pszString = ( bNewMethod ) ? "#TF_PVE_Buyback_Fixed" : "#TF_PVE_Buyback";
+
+ Q_snprintf( szSecs, sizeof( szSecs ), "%d", nCost );
+ g_pVGuiLocalize->ConvertANSIToUnicode( szSecs, wSecs, sizeof( wSecs ) );
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( pszString ), 1, wSecs );
+
+ wchar_t wBuyBack[256];
+ UTIL_ReplaceKeyBindings( wLabel, 0, wBuyBack, sizeof( wBuyBack ), GAME_ACTION_SET_SPECTATOR );
+
+ m_pBuyBackLabel->SetText( wBuyBack, true );
+ }
+ }
+
+ Q_snprintf( szSecs, sizeof(szSecs), "%d", iRespawnWait );
+
+ g_pVGuiLocalize->ConvertANSIToUnicode(szSecs, wSecs, sizeof(wSecs));
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_secs" ), 1, wSecs );
+ }
+ }
+
+ m_pReinforcementsLabel->SetVisible( true );
+ m_pReinforcementsLabel->SetText( wLabel, true );
+ m_pBuyBackLabel->SetVisible( bBuyBackVisible );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::UpdateKeyLabels( void )
+{
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ bool bSteamController = ::input->IsSteamControllerActive();
+
+ if ( InTournamentGUI() == false )
+ {
+ // get the desired player class
+ int iClass = TF_CLASS_UNDEFINED;
+ bool bIsHLTV = engine->IsHLTV();
+
+ if ( pPlayer )
+ {
+ iClass = pPlayer->m_Shared.GetDesiredPlayerClassIndex();
+ }
+
+ // if it's time to change the tip, or the player has changed desired class, update the tip
+ if ( ( gpGlobals->curtime >= m_flNextTipChangeTime ) || ( iClass != m_iTipClass ) )
+ {
+ if ( bIsHLTV )
+ {
+ const wchar_t *wzTip = g_pVGuiLocalize->Find( "#Tip_HLTV" );
+
+ if ( wzTip )
+ {
+ SetDialogVariable( "tip", wzTip );
+ }
+ }
+ else
+ {
+ wchar_t wzTipLabel[512]=L"";
+ const wchar_t *wzTip = g_TFTips.GetNextClassTip( iClass );
+ Assert( wzTip && wzTip[0] );
+ g_pVGuiLocalize->ConstructString_safe( wzTipLabel, g_pVGuiLocalize->Find( "#Tip_Fmt" ), 1, wzTip );
+ SetDialogVariable( "tip", wzTipLabel );
+ }
+
+ m_flNextTipChangeTime = gpGlobals->curtime + 10.0f;
+ m_iTipClass = iClass;
+ }
+
+ if ( m_pClassOrTeamLabel )
+ {
+ if ( pPlayer )
+ {
+ static wchar_t wzFinal[512] = L"";
+ const wchar_t *wzTemp = NULL;
+ const wchar_t *wzIcon = nullptr;
+
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ {
+ wzTemp = L"";
+ wzIcon = L"";
+ }
+ else if ( bIsHLTV )
+ {
+ wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_AutoDirector" );
+ }
+ else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR && TFGameRules()->IsInArenaMode() == false )
+ {
+ if ( bSteamController )
+ {
+ wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam_NoKey" );
+ wzIcon = GetSCGlyph( "changeteam" );
+ }
+ else
+ {
+ wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam" );
+ }
+ }
+ else
+ {
+ if ( bSteamController )
+ {
+ wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass_NoKey" );
+ wzIcon = GetSCGlyph( "changeclass" );
+ }
+ else
+ {
+ wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass" );
+ }
+ }
+
+ if ( wzTemp )
+ {
+ UTIL_ReplaceKeyBindings( wzTemp, 0, wzFinal, sizeof( wzFinal ) );
+ }
+
+ m_pClassOrTeamLabel->SetText( wzFinal, true );
+
+ if ( m_pClassOrTeamKeyLabel )
+ {
+ if ( wzIcon && m_pClassOrTeamLabel->IsVisible() )
+ {
+ m_pClassOrTeamKeyLabel->SetText( wzIcon );
+ m_pClassOrTeamKeyLabel->SetVisible( true );
+ }
+ else
+ {
+ m_pClassOrTeamKeyLabel->SetVisible( false );
+ }
+ }
+ }
+ }
+
+ static ConVarRef cl_hud_minmode( "cl_hud_minmode", true );
+ if ( m_bCoaching == true || ( cl_hud_minmode.IsValid() && ( cl_hud_minmode.GetBool() == false ) ) )
+ {
+ if ( m_pSwitchCamModeKeyLabel )
+ {
+ if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( ( mp_forcecamera.GetInt() == OBS_ALLOW_TEAM ) || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) || mp_fadetoblack.GetBool() ) )
+ {
+ if ( m_pSwitchCamModeKeyLabel->IsVisible() )
+ {
+ m_pSwitchCamModeKeyLabel->SetVisible( false );
+
+ Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( false );
+ }
+ }
+ }
+ else
+ {
+ if ( !m_pSwitchCamModeKeyLabel->IsVisible() )
+ {
+ m_pSwitchCamModeKeyLabel->SetVisible( true );
+
+ Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( true );
+ }
+ }
+
+ wchar_t wLabel[256] = L"";
+ const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_SwitchCamModeKey" );
+ UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
+ m_pSwitchCamModeKeyLabel->SetText( wLabel, true );
+ }
+ }
+
+ if ( m_pCycleTargetFwdKeyLabel )
+ {
+ if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) )
+ {
+ if ( m_pCycleTargetFwdKeyLabel->IsVisible() )
+ {
+ m_pCycleTargetFwdKeyLabel->SetVisible( false );
+
+ Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( false );
+ }
+ }
+ }
+ else
+ {
+ if ( !m_pCycleTargetFwdKeyLabel->IsVisible() )
+ {
+ m_pCycleTargetFwdKeyLabel->SetVisible( true );
+
+ Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( true );
+ }
+ }
+
+ if ( !bSteamController )
+ {
+ wchar_t wLabel[256] = L"";
+ const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetFwdKey" );
+ UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
+ m_pCycleTargetFwdKeyLabel->SetText( wLabel, true );
+ }
+ else
+ {
+ m_pCycleTargetFwdKeyLabel->SetText( GetSCGlyph( "next_target" ) );
+ }
+ }
+ }
+
+ if ( m_pCycleTargetRevKeyLabel )
+ {
+ if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) )
+ {
+ if ( m_pCycleTargetRevKeyLabel->IsVisible() )
+ {
+ m_pCycleTargetRevKeyLabel->SetVisible( false );
+
+ Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( false );
+ }
+ }
+ }
+ else
+ {
+ if ( !m_pCycleTargetRevKeyLabel->IsVisible() )
+ {
+ m_pCycleTargetRevKeyLabel->SetVisible( true );
+
+ Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( true );
+ }
+ }
+
+ if ( !bSteamController )
+ {
+ wchar_t wLabel[256] = L"";
+ const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetRevKey" );
+ UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
+ m_pCycleTargetRevKeyLabel->SetText( wLabel, true );
+ }
+ else
+ {
+ m_pCycleTargetRevKeyLabel->SetText( GetSCGlyph( "prev_target" ) );
+ }
+ }
+ }
+
+ if ( m_pMapLabel )
+ {
+ wchar_t wMapName[32];
+ wchar_t wLabel[256];
+ char szMapName[32];
+
+ char tempname[128];
+ Q_FileBase( engine->GetLevelName(), tempname, sizeof( tempname ) );
+ Q_strlower( tempname );
+
+ if ( IsX360() )
+ {
+ char *pExt = Q_stristr( tempname, ".360" );
+ if ( pExt )
+ {
+ *pExt = '\0';
+ }
+ }
+
+ Q_strncpy( szMapName, GetMapDisplayName( tempname ), sizeof( szMapName ) );
+
+ g_pVGuiLocalize->ConvertANSIToUnicode( szMapName, wMapName, sizeof(wMapName));
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#Spec_Map" ), 1, wMapName );
+
+ m_pMapLabel->SetText( wLabel );
+ }
+ }
+ }
+
+ // coaching stuff
+ if ( pPlayer && pPlayer->m_hStudent )
+ {
+ int iHealth = 0;
+ int iMaxHealth = 1;
+ int iMaxBuffedHealth = 0;
+
+ C_TFPlayer *pStudent = pPlayer->m_hStudent;
+ {
+ wchar_t wPlayerName[MAX_PLAYER_NAME_LENGTH];
+ wchar_t wLabel[256];
+ const char* pStudentName = g_TF_PR->GetPlayerName( pStudent->entindex() );
+
+ g_pVGuiLocalize->ConvertANSIToUnicode( pStudentName, wPlayerName, sizeof(wPlayerName));
+ g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Coach_Student_Prefix" ), 1, wPlayerName );
+
+ SetDialogVariable( "student_name", wLabel );
+ }
+ for ( int i = 1; i <= 2; ++i )
+ {
+ wchar_t wLabel[256] = L"";
+ const wchar_t *wzTemp = g_pVGuiLocalize->Find( CFmtStr1024( "#TF_Coach_Slot%uLabel", i ) );
+ UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
+ SetDialogVariable( CFmtStr1024( "coach_command_%u", i ), wLabel );
+ }
+ if ( m_pAvatar )
+ {
+ m_pAvatar->SetShouldDrawFriendIcon( false );
+
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ CSteamID studentSteamID;
+ if ( pStudent->GetSteamID( &studentSteamID ) )
+ {
+ m_pAvatar->SetPlayer( studentSteamID, k_EAvatarSize64x64 );
+ }
+ else
+ {
+ m_pAvatar->ClearAvatar();
+ }
+ }
+ }
+
+ // don't show crosshair when viewing the world from the student's POV
+ bool bShowCrosshair = pPlayer->GetObserverMode() != OBS_MODE_IN_EYE;
+ vgui::Panel *pCrosshair = FindChildByName( "Crosshair" );
+ if ( pCrosshair && pCrosshair->IsVisible() != bShowCrosshair )
+ {
+ pCrosshair->SetVisible( bShowCrosshair );
+ }
+
+ iHealth = pStudent->GetHealth();
+ iMaxHealth = pStudent->GetMaxHealth();
+ iMaxBuffedHealth = pStudent->m_Shared.GetMaxBuffedHealth();
+
+ if ( m_pStudentHealth )
+ {
+ m_pStudentHealth->SetHealth( iHealth, iMaxHealth, iMaxBuffedHealth );
+
+ if ( !m_pStudentHealth->IsVisible() )
+ {
+ m_pStudentHealth->SetVisible( true );
+ }
+ }
+ }
+ else
+ {
+ if ( m_pStudentHealth && m_pStudentHealth->IsVisible() )
+ {
+ m_pStudentHealth->SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::ShowPanel(bool bShow)
+{
+ if ( bShow != IsVisible() )
+ {
+ CTFHudObjectiveStatus *pStatus = GET_HUDELEMENT( CTFHudObjectiveStatus );
+ CHudBuildingStatusContainer_Engineer *pEngBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Engineer, BuildingStatus_Engineer );
+ CHudBuildingStatusContainer_Spy *pSpyBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Spy, BuildingStatus_Spy );
+ CHudItemAttributeTracker *pAttribTrackers = GET_HUDELEMENT( CHudItemAttributeTracker );
+
+ if ( pAttribTrackers )
+ {
+ pAttribTrackers->InvalidateLayout();
+ }
+
+ if ( bShow )
+ {
+ int xPos = 0, yPos = 0;
+
+ if ( pStatus )
+ {
+ pStatus->SetParent( this );
+ pStatus->SetProportional( true );
+ }
+
+ if ( pEngBuilds )
+ {
+ pEngBuilds->GetPos( xPos, yPos );
+ m_nEngBuilds_xpos = xPos;
+ m_nEngBuilds_ypos = yPos;
+ pEngBuilds->SetPos( xPos, GetTopBarHeight() );
+ }
+
+ if ( pSpyBuilds )
+ {
+ pSpyBuilds->GetPos( xPos, yPos );
+ m_nSpyBuilds_xpos = xPos;
+ m_nSpyBuilds_ypos = yPos;
+ pSpyBuilds->SetPos( xPos, GetTopBarHeight() );
+ }
+
+#if defined( REPLAY_ENABLED )
+ // We don't want to display this message the first time the spectator GUI is shown, since that is right
+ // when the class menu is shown. We use the player spawn cache here - which is a terrible name for something
+ // very useful - which will nuke m_nDisplaySaveReplay every time a new map is loaded, which is exactly what
+ // we want.
+ int &nDisplaySaveReplay = CPlayerSpawnCache::Instance().m_Data.m_nDisplaySaveReplay;
+ extern IReplayManager *g_pReplayManager;
+ CReplay *pCurLifeReplay = ( g_pReplayManager ) ? g_pReplayManager->GetReplayForCurrentLife() : NULL;
+ if ( g_pReplay->IsRecording() &&
+ !engine->IsPlayingDemo() &&
+ !::input->IsSteamControllerActive() &&
+ nDisplaySaveReplay &&
+ ( pCurLifeReplay && !pCurLifeReplay->m_bRequestedByUser && !pCurLifeReplay->m_bSaved ) )
+ {
+ wchar_t wText[256];
+ wchar wKeyBind[80];
+ char szText[256 * sizeof(wchar_t)];
+
+ const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" );
+ if ( !pSaveReplayKey )
+ {
+ pSaveReplayKey = "< not bound >";
+ }
+ g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) );
+ g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_SaveThisLifeMsg" ), 1, wKeyBind );
+ g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) );
+
+ g_pClientMode->DisplayReplayMessage( szText, -1.0f, false, NULL, false );
+ }
+ ++nDisplaySaveReplay;
+#endif
+
+ m_flNextTipChangeTime = 0; // force a new tip immediately
+
+ InvalidateLayout();
+ }
+ else
+ {
+ if ( pStatus )
+ {
+ pStatus->SetParent( g_pClientMode->GetViewport() );
+ }
+
+ if ( pEngBuilds )
+ {
+ pEngBuilds->SetPos( m_nEngBuilds_xpos, m_nEngBuilds_ypos );
+ }
+
+ if ( pSpyBuilds )
+ {
+ pSpyBuilds->SetPos( m_nSpyBuilds_xpos, m_nSpyBuilds_ypos );
+ }
+ }
+
+ UpdateKeyLabels();
+
+ if ( bShow )
+ {
+ m_flNextPlayerPanelUpdate = 0;
+ m_flNextItemPanelUpdate = 0;
+ UpdateItemPanel();
+ RecalculatePlayerPanels();
+ }
+ }
+
+ BaseClass::ShowPanel( bShow );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::FireGameEvent( IGameEvent *event )
+{
+ const char *pEventName = event->GetName();
+
+ if ( Q_strcmp( "spec_target_updated", pEventName ) == 0 )
+ {
+ UpdateItemPanel();
+ }
+ else if ( Q_strcmp( "player_death", pEventName ) == 0 && m_bCoaching )
+ {
+ CBaseEntity *pVictim = ClientEntityList().GetEnt( engine->GetPlayerForUserID( event->GetInt("userid") ) );
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer && ( pVictim == pLocalPlayer->m_hStudent ) )
+ {
+ CEconNotification *pNotification = new CEconNotification();
+ pNotification->SetText( "#TF_Coach_StudentHasDied" );
+ pNotification->SetLifetime( 10.0f );
+ pNotification->SetSoundFilename( "coach/coach_student_died.wav" );
+ NotificationQueue_Add( pNotification );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::UpdateItemPanel( bool bForce )
+{
+ bool bVisible = false;
+
+ // Stat panel prevents the item panel from showing.
+ CTFStatPanel *pStatPanel = GET_HUDELEMENT( CTFStatPanel );
+ if ( ( pStatPanel && pStatPanel->IsVisible() ) || ( TFGameRules() && TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) || (!cl_spec_carrieditems.GetBool() && !bForce) )
+ {
+ bVisible = false;
+ }
+ else
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer )
+ {
+ C_TFPlayer *pPlayer = ToTFPlayer( pLocalPlayer->GetObserverTarget() );
+ if ( pPlayer && pPlayer != pLocalPlayer )
+ {
+ if ( m_flNextItemPanelUpdate && m_flNextItemPanelUpdate > gpGlobals->curtime && m_hPrevItemPlayer == pPlayer )
+ return;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
+ return;
+
+ if ( m_hPrevItemPlayer != pPlayer )
+ {
+ m_iPrevItemShown = 0;
+ m_iFirstItemShown = 0;
+ m_bShownItems = false;
+ m_hPrevItemPlayer = pPlayer;
+ }
+ m_flNextItemPanelUpdate = gpGlobals->curtime + 8.0;
+
+ // Don't reshow the items for a player unless we're being forced to
+ if ( !m_bShownItems || bForce )
+ {
+ // If our killer is using a non-standard item, display its stats.
+ // Loop through all items and pick one at random, so that we show non-active weapons as well.
+ CEconItemView *pItemToShow = pPlayer->GetInspectItem( &m_iPrevItemShown );
+
+ // If we've looped, we're done. Hide the item.
+ if ( m_iFirstItemShown && m_iFirstItemShown == m_iPrevItemShown )
+ {
+ m_iFirstItemShown = 0;
+ m_iPrevItemShown = 0;
+ pItemToShow = NULL;
+ m_bShownItems = true;
+ }
+
+ if ( pItemToShow )
+ {
+ if ( !m_iFirstItemShown )
+ {
+ m_iFirstItemShown = m_iPrevItemShown;
+ }
+
+ Label* pItemLabel = m_pItemPanel->FindControl<Label>( "ItemLabel" );
+
+ // Change the label text depending on if the original owner is holding the weapon
+ if ( pItemLabel )
+ {
+ CSteamID steamIDOwner;
+ pPlayer->GetSteamID( &steamIDOwner );
+ bool bOriginalOwner = steamIDOwner.GetAccountID() == pItemToShow->GetAccountID();
+ pItemLabel->SetText( bOriginalOwner ? "#FreezePanel_Item" : "#FreezePanel_ItemOtherOwner" );
+ }
+
+
+ bVisible = true;
+ m_pItemPanel->SetDialogVariable( "killername", g_TF_PR->GetPlayerName( pPlayer->entindex() ) );
+
+ // Set the item owner's name
+ CBasePlayer *pOriginalOwner = GetPlayerByAccountID( pItemToShow->GetAccountID() );
+ if ( pOriginalOwner )
+ {
+ m_pItemPanel->SetDialogVariable( "ownername", g_TF_PR->GetPlayerName( pOriginalOwner->entindex() ) );
+ }
+
+ m_pItemPanel->SetItem( pItemToShow );
+
+ // force update description to get the correct panel size
+ m_pItemPanel->UpdateDescription();
+ m_pItemPanel->SetPos( ScreenWidth() - XRES( 10 ) - m_pItemPanel->GetWide(), ScreenHeight() - YRES( 12 ) - m_pItemPanel->GetTall() );
+ }
+ }
+ }
+ }
+ }
+
+ if ( m_pItemPanel->IsVisible() != bVisible )
+ {
+ m_pItemPanel->SetVisible( bVisible );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::ForceItemPanelCycle( void )
+{
+ m_flNextItemPanelUpdate = 0;
+ UpdateItemPanel( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFSpectatorGUI::GetResFile( void )
+{
+ if ( m_bCoaching )
+ {
+ return "Resource/UI/SpectatorCoach.res";
+ }
+ else if ( InTournamentGUI() )
+ {
+ return "Resource/UI/SpectatorTournament.res";
+ }
+ else if ( ::input->IsSteamControllerActive() )
+ {
+ return "Resource/UI/Spectator_SC.res";
+ }
+ else
+ {
+ return "Resource/UI/Spectator.res";
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFSpectatorGUI::InTournamentGUI( void )
+{
+ bool bOverride = false;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ bOverride = true;
+ }
+
+ return ( TFGameRules()->IsInTournamentMode() && !TFGameRules()->IsCompetitiveMode() && ( cl_use_tournament_specgui.GetBool() || bOverride ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::RecalculatePlayerPanels( void )
+{
+ if ( !InTournamentGUI() )
+ {
+ if ( m_PlayerPanels.Count() > 0 )
+ {
+ // Delete any player panels we have, we've turned off the tourney GUI.
+ for ( int i = m_PlayerPanels.Count()-1; i >= 0; i-- )
+ {
+ m_PlayerPanels[i]->MarkForDeletion();
+ }
+ m_PlayerPanels.Purge();
+ }
+
+ return;
+ }
+
+ C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( !pPlayer || !g_TF_PR || !TFGameRules() )
+ return;
+
+ int iLocalTeam = pPlayer->GetTeamNumber();
+ bool bMvM = TFGameRules()->IsMannVsMachineMode();
+
+ // Calculate the number of players that must be shown. Spectators see all players (except in MvM), team members only see their team.
+ int iPanel = 0;
+
+ for ( int nClass = TF_FIRST_NORMAL_CLASS; nClass <= TF_LAST_NORMAL_CLASS; nClass++ )
+ {
+ // we want to sort the images to match the class menu selections
+ int nCurrentClass = g_ClassDefinesRemap[nClass];
+
+ for ( int i = 0; i < MAX_PLAYERS; i++ )
+ {
+ int iPlayer = i+1;
+ if ( !g_TF_PR->IsConnected( iPlayer ) )
+ continue;
+
+ bool bHideBots = false;
+
+ #ifndef _DEBUG
+ if ( bMvM )
+ {
+ bHideBots = true;
+ }
+ #endif
+ int iTeam = g_TF_PR->GetTeam( iPlayer );
+ if ( iTeam != iLocalTeam && iLocalTeam != TEAM_SPECTATOR )
+ continue;
+ if ( iTeam != TF_TEAM_RED && iTeam != TF_TEAM_BLUE )
+ continue;
+ if ( g_TF_PR->IsFakePlayer( iPlayer ) && bHideBots )
+ continue;
+ if ( bMvM && ( iTeam == TF_TEAM_PVE_INVADERS ) )
+ continue;
+ if ( g_TF_PR->GetPlayerClass( iPlayer ) != nCurrentClass )
+ continue;
+
+ if ( m_PlayerPanels.Count() <= iPanel )
+ {
+ CTFPlayerPanel *pPanel = new CTFPlayerPanel( this, VarArgs("playerpanel%d", i) );
+ if ( m_pPlayerPanelKVs )
+ {
+ pPanel->ApplySettings( m_pPlayerPanelKVs );
+ }
+ m_PlayerPanels.AddToTail( pPanel );
+ }
+
+ m_PlayerPanels[iPanel]->SetPlayerIndex( iPlayer );
+ iPanel++;
+ }
+ }
+
+ for ( int i = iPanel; i < m_PlayerPanels.Count(); i++ )
+ {
+ m_PlayerPanels[i]->SetPlayerIndex( 0 );
+ }
+
+ UpdatePlayerPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::UpdatePlayerPanels( void )
+{
+ if ( !g_TF_PR )
+ return;
+
+ uint nVisible = 0;
+ bool bNeedsPlayerLayout = false;
+ for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
+ {
+ if ( m_PlayerPanels[i]->Update() )
+ {
+ bNeedsPlayerLayout = true;
+ }
+
+ if ( m_PlayerPanels[i]->IsVisible() )
+ {
+ nVisible++;
+ }
+ }
+
+ if ( !bNeedsPlayerLayout )
+ return;
+
+ // Try and always put the local player's team on team1, if he's in a team
+ int iTeam1 = TF_TEAM_BLUE;
+ int iTeam2 = TF_TEAM_RED;
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( !pLocalPlayer )
+ return;
+
+ int iLocalTeam = g_TF_PR->GetTeam( pLocalPlayer->entindex() );
+
+ if ( iLocalTeam == TF_TEAM_RED || iLocalTeam == TF_TEAM_BLUE )
+ {
+ iTeam1 = iLocalTeam;
+ iTeam2 = ( iTeam1 == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
+ }
+
+ int iTeam1Count = 0;
+ int iTeam2Count = 0;
+ int iCenter = GetWide() * 0.5;
+ int iYPosOverride = 0;
+
+ // We only want to draw the player panels for the defenders in MvM
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ iTeam1 = TF_TEAM_PVE_DEFENDERS;
+ iTeam2 = TF_TEAM_PVE_INVADERS;
+
+ int iNumValidPanels = 0;
+ for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
+ {
+ if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 )
+ {
+ continue;
+ }
+
+ iNumValidPanels++;
+ }
+
+ // we'll center the group of players in MvM
+ m_iTeam1PlayerBaseOffsetX = ( iNumValidPanels * m_iTeam1PlayerDeltaX ) * -0.5;
+
+ if ( iLocalTeam > LAST_SHARED_TEAM )
+ {
+ if ( m_pTopBar )
+ {
+ iYPosOverride = m_pTopBar->GetTall();
+ }
+ }
+ }
+
+ for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
+ {
+ int iXPos = 0;
+ int iYPos = 0;
+
+ if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 )
+ {
+ m_PlayerPanels[i]->SetVisible( false );
+ continue;
+ }
+
+ int iTeam = g_TF_PR->GetTeam( m_PlayerPanels[i]->GetPlayerIndex() );
+ if ( iTeam == iTeam1 )
+ {
+ iXPos = m_iTeam1PlayerBaseOffsetX ? (iCenter + m_iTeam1PlayerBaseOffsetX) : m_iTeam1PlayerBaseX;
+ iXPos += (iTeam1Count * m_iTeam1PlayerDeltaX);
+ iYPos = iYPosOverride ? iYPosOverride : m_iTeam1PlayerBaseY + (iTeam1Count * m_iTeam1PlayerDeltaY);
+ m_PlayerPanels[i]->SetSpecIndex( 6 - iTeam1Count );
+ m_PlayerPanels[i]->SetPos( iXPos, iYPos );
+ m_PlayerPanels[i]->SetVisible( true );
+ iTeam1Count++;
+ }
+ else if ( iTeam == iTeam2 )
+ {
+ iXPos = m_iTeam2PlayerBaseOffsetX ? (iCenter + m_iTeam2PlayerBaseOffsetX) : m_iTeam2PlayerBaseX;
+ iXPos += (iTeam2Count * m_iTeam2PlayerDeltaX);
+ iYPos = iYPosOverride ? iYPosOverride : m_iTeam2PlayerBaseY + (iTeam2Count * m_iTeam2PlayerDeltaY);
+ m_PlayerPanels[i]->SetSpecIndex( 7 + iTeam2Count );
+ m_PlayerPanels[i]->SetPos( iXPos, iYPos );
+ m_PlayerPanels[i]->SetVisible( true );
+ iTeam2Count++;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFSpectatorGUI::SelectSpec( int iSlot )
+{
+ if ( m_bCoaching )
+ {
+ engine->ClientCmd_Unrestricted( CFmtStr1024( "coach_command %u", iSlot ) );
+ return;
+ }
+
+ if ( !InTournamentGUI() )
+ return;
+
+ for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
+ {
+ if ( m_PlayerPanels[i]->GetSpecIndex() == iSlot )
+ {
+ engine->ClientCmd_Unrestricted( VarArgs( "spec_player %d\n", m_PlayerPanels[i]->GetPlayerIndex() ) );
+ return;
+ }
+ }
+}
diff --git a/game/client/tf/vgui/tf_spectatorgui.h b/game/client/tf/vgui/tf_spectatorgui.h
new file mode 100644
index 0000000..813276e
--- /dev/null
+++ b/game/client/tf/vgui/tf_spectatorgui.h
@@ -0,0 +1,140 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef TF_SPECTATORGUI_H
+#define TF_SPECTATORGUI_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <spectatorgui.h>
+#include "hudelement.h"
+#include "tf_hud_playerstatus.h"
+#include "item_model_panel.h"
+#include "tf_gamerules.h"
+#include <vgui_controls/EditablePanel.h>
+
+extern ConVar cl_use_tournament_specgui;
+class CAvatarImagePanel;
+class CTFPlayerPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose: Custom health panel used to show spectator target's health
+//-----------------------------------------------------------------------------
+class CTFSpectatorGUIHealth : public CTFHudPlayerHealth
+{
+public:
+ CTFSpectatorGUIHealth( Panel *parent, const char *name ) : CTFHudPlayerHealth( parent, name )
+ {
+ }
+
+ virtual const char *GetResFilename( void )
+ {
+ return "resource/UI/SpectatorGUIHealth.res";
+ }
+ virtual void OnThink()
+ {
+ // Do nothing. We're just preventing the base health panel from updating.
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: TF Spectator UI
+//-----------------------------------------------------------------------------
+class CTFSpectatorGUI : public CSpectatorGUI, public CGameEventListener
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFSpectatorGUI, CSpectatorGUI );
+
+public:
+ CTFSpectatorGUI( IViewPort *pViewPort );
+ ~CTFSpectatorGUI( void );
+
+ virtual void Reset( void );
+ virtual void PerformLayout( void );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+
+ virtual void Update( void );
+ virtual bool NeedsUpdate( void );
+ virtual bool ShouldShowPlayerLabel( int specmode ) { return false; }
+ void UpdateReinforcements( void );
+ virtual void ShowPanel(bool bShow);
+ virtual Color GetBlackBarColor( void ) { return Color(52,48,45, 255); }
+
+ void UpdateKeyLabels( void );
+
+ virtual void FireGameEvent( IGameEvent *event );
+ void UpdateItemPanel( bool bForce = false );
+ void ForceItemPanelCycle( void );
+ virtual const char *GetResFile( void );
+
+ // Tournament mode handling
+ bool InTournamentGUI( void );
+ void RecalculatePlayerPanels( void );
+ void UpdatePlayerPanels( void );
+ void SelectSpec( int iSlot );
+
+ virtual int GetTopBarHeight();
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_SPECTATOR; }
+
+protected:
+ int m_nLastSpecMode;
+ float m_flNextTipChangeTime; // time at which to next change the tip
+ int m_iTipClass; // class that current tip is for
+
+ // used to store the x and y position of the Engy and Spy build panels so we can reset them when the spec panel goes away
+ int m_nEngBuilds_xpos;
+ int m_nEngBuilds_ypos;
+ int m_nSpyBuilds_xpos;
+ int m_nSpyBuilds_ypos;
+
+ int m_nMannVsMachineStatus_xpos;
+ int m_nMannVsMachineStatus_ypos;
+
+ vgui::Label *m_pReinforcementsLabel;
+ CExLabel *m_pBuyBackLabel;
+ vgui::Label *m_pClassOrTeamLabel;
+ CExLabel *m_pClassOrTeamKeyLabel;
+ vgui::Label *m_pSwitchCamModeKeyLabel;
+ vgui::Label *m_pCycleTargetFwdKeyLabel;
+ vgui::Label *m_pCycleTargetRevKeyLabel;
+ vgui::Label *m_pMapLabel;
+ CItemModelPanel *m_pItemPanel;
+
+ float m_flNextItemPanelUpdate;
+ EHANDLE m_hPrevItemPlayer;
+ int m_iPrevItemShown;
+ int m_iFirstItemShown;
+ bool m_bShownItems;
+
+ // Tournament mode player panel handling
+ CUtlVector<CTFPlayerPanel*> m_PlayerPanels;
+ KeyValues *m_pPlayerPanelKVs;
+ bool m_bReapplyPlayerPanelKVs;
+ bool m_bPrevTournamentMode;
+ float m_flNextPlayerPanelUpdate;
+
+ // Coaching
+ bool m_bCoaching;
+ CAvatarImagePanel *m_pAvatar;
+ CTFSpectatorGUIHealth *m_pStudentHealth;
+
+ CPanelAnimationVarAliasType( int, m_iTeam1PlayerBaseOffsetX, "team1_player_base_offset_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam1PlayerBaseX, "team1_player_base_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam1PlayerBaseY, "team1_player_base_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam2PlayerBaseX, "team2_player_base_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam2PlayerBaseOffsetX, "team2_player_base_offset_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam2PlayerBaseY, "team2_player_base_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam1PlayerDeltaX, "team1_player_delta_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam1PlayerDeltaY, "team1_player_delta_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam2PlayerDeltaX, "team2_player_delta_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTeam2PlayerDeltaY, "team2_player_delta_y", "0", "proportional_int" );
+};
+
+#endif // TF_SPECTATORGUI_H
diff --git a/game/client/tf/vgui/tf_statsummary.cpp b/game/client/tf/vgui/tf_statsummary.cpp
new file mode 100644
index 0000000..8e96829
--- /dev/null
+++ b/game/client/tf/vgui/tf_statsummary.cpp
@@ -0,0 +1,1506 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#include "cbase.h"
+
+#include <vgui_controls/Label.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ComboBox.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui_controls/RichText.h>
+#include <vgui_controls/Frame.h>
+#include <vgui_controls/QueryBox.h>
+#include <vgui/IScheme.h>
+#include <vgui/ILocalize.h>
+#include <vgui/ISurface.h>
+#include "ienginevgui.h"
+#include <game/client/iviewport.h>
+#include "tf_tips.h"
+#include "tf_mapinfo.h"
+#include "vgui_avatarimage.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+
+#include "tf_statsummary.h"
+#include <convar.h>
+#include "fmtstr.h"
+#include "tf_gamerules.h"
+#include "tf_gc_client.h"
+
+using namespace vgui;
+
+#if defined( REPLAY_ENABLED )
+extern bool g_bIsReplayRewinding;
+#else
+bool g_bIsReplayRewinding = false;
+#endif
+
+const char *g_pszTipsClassImages[] =
+{
+ "", // TF_CLASS_UNDEFINED = 0,
+ "class_portraits/scout", // TF_CLASS_SCOUT,
+ "class_portraits/sniper",// TF_CLASS_SNIPER,
+ "class_portraits/soldier", // TF_CLASS_SOLDIER,
+ "class_portraits/demoman", // TF_CLASS_DEMOMAN,
+ "class_portraits/medic", // TF_CLASS_MEDIC,
+ "class_portraits/heavy", // TF_CLASS_HEAVYWEAPONS,
+ "class_portraits/pyro", // TF_CLASS_PYRO,
+ "class_portraits/spy", // TF_CLASS_SPY,
+ "class_portraits/engineer", // TF_CLASS_ENGINEER,
+};
+
+ClassDetails_t g_PerClassStatDetails[15] =
+{
+ { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" },
+ { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" },
+ { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" },
+ { TFSTAT_CAPTURES, ALL_CLASSES, "#TF_ClassRecord_MostCaptures", "#TF_ClassRecord_Alt_MostCaptures" },
+ { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" },
+ { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" },
+ { TFSTAT_BUILDINGSDESTROYED, ALL_CLASSES, "#TF_ClassRecord_MostDestruction", "#TF_ClassRecord_Alt_MostDestruction" },
+ { TFSTAT_DOMINATIONS, ALL_CLASSES, "#TF_ClassRecord_MostDominations", "#TF_ClassRecord_Alt_MostDominations" },
+ { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" },
+ { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" },
+ { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" },
+ { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" },
+ { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" },
+ { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" },
+ { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" },
+};
+
+ClassDetails_t g_PerClassMVMStatDetails[12] =
+{
+ { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" },
+ { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" },
+ { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" },
+ { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" },
+ { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" },
+ { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" },
+ { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" },
+ { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" },
+ { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" },
+ { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" },
+ { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" },
+ { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" },
+};
+
+CTFStatsSummaryPanel *g_pTFStatsSummaryPanel = NULL;
+
+CUtlVector<CTFStatsSummaryPanel *> g_vecStatPanels;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void UpdateStatSummaryPanels( CUtlVector<ClassStats_t> &vecClassStats )
+{
+ for ( int i = 0; i < g_vecStatPanels.Count(); i++ )
+ {
+ g_vecStatPanels[i]->SetStats( vecClassStats );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the global stats summary panel
+//-----------------------------------------------------------------------------
+CTFStatsSummaryPanel *GStatsSummaryPanel()
+{
+ if ( NULL == g_pTFStatsSummaryPanel )
+ {
+ g_pTFStatsSummaryPanel = new CTFStatsSummaryPanel();
+ }
+ return g_pTFStatsSummaryPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destroys the global stats summary panel
+//-----------------------------------------------------------------------------
+void DestroyStatsSummaryPanel()
+{
+ if ( NULL != g_pTFStatsSummaryPanel )
+ {
+ g_pTFStatsSummaryPanel->MarkForDeletion();
+ g_pTFStatsSummaryPanel = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFStatsSummaryPanel::CTFStatsSummaryPanel()
+ : BaseClass( NULL, "TFStatsSummary", vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) )
+ , m_bShowingLeaderboard( false )
+ , m_bLoadingCommunityMap( false )
+{
+ Init();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::Init( void )
+{
+ m_bControlsLoaded = false;
+ m_bInteractive = false;
+ m_bEmbedded = false;
+ m_xStartLHBar = 0;
+ m_xStartRHBar = 0;
+ m_iBarHeight = 1;
+ m_iBarMaxWidth = 1;
+
+ m_pPlayerData = new vgui::EditablePanel( this, "statdata" );
+ m_pInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "InteractiveHeaders" );
+ m_pNonInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "NonInteractiveHeaders" );
+ m_pBarChartComboBoxA = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboA", 10, false );
+ m_pBarChartComboBoxB = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboB", 10, false );
+ m_pClassComboBox = new vgui::ComboBox( m_pInteractiveHeaders, "ClassCombo", 10, false );
+ m_pTipImage = new CTFImagePanel( this, "TipImage" );
+ m_pTipText = new vgui::Label( this, "TipText", "" );
+ m_pMapInfoPanel = NULL;
+ m_pMainBackground = NULL;
+ m_pLeaderboardTitle = NULL;
+ m_pContributedPanel = NULL;
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+ m_bShowBackButton = false;
+#else
+ m_pNextTipButton = new vgui::Button( this, "NextTipButton", "" );
+ m_pResetStatsButton = new vgui::Button( this, "ResetStatsButton", "" );
+ m_pCloseButton = new vgui::Button( this, "CloseButton", "" );
+#endif
+
+ m_pBarChartComboBoxA->AddActionSignalTarget( this );
+ m_pBarChartComboBoxB->AddActionSignalTarget( this );
+ m_pClassComboBox->AddActionSignalTarget( this );
+
+ ListenForGameEvent( "server_spawn" );
+
+ Reset();
+
+ g_vecStatPanels.AddToTail( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFStatsSummaryPanel::CTFStatsSummaryPanel( vgui::Panel *parent ) : BaseClass( parent, "TFStatsSummary",
+ vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) )
+{
+ Init();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStatsSummaryPanel::~CTFStatsSummaryPanel()
+{
+ g_vecStatPanels.FindAndRemove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shows this dialog as a modal dialog
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::ShowModal()
+{
+#ifdef _X360
+ m_bInteractive = false;
+ m_bShowBackButton = true;
+#else
+ // we are in interactive mode, enable controls
+ m_bInteractive = true;
+#endif
+
+ SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) );
+ UpdateDialog();
+ SetVisible( true );
+ MoveToFront();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::SetupForEmbedded( void )
+{
+ m_bInteractive = true;
+ m_bEmbedded = true;
+
+ UpdateDialog();
+
+ InvalidateLayout( true, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+#ifndef _X360
+ if ( m_pTipImage && m_pTipText )
+ {
+ int iX,iY;
+ m_pTipImage->GetPos(iX,iY);
+ int iTX, iTY;
+ m_pTipText->GetPos(iTX, iTY);
+ m_pTipText->SetPos( iX + m_pTipImage->GetWide() + XRES(8), iTY );
+ }
+
+ if ( m_pNextTipButton )
+ {
+ m_pNextTipButton->SizeToContents();
+ }
+
+ if ( m_pResetStatsButton )
+ {
+ m_pResetStatsButton->SizeToContents();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( m_bShowingLeaderboard )
+ {
+ UpdateLeaderboard();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Command handler
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::OnCommand( const char *command )
+{
+ if ( 0 == Q_stricmp( command, "vguicancel" ) )
+ {
+ m_bInteractive = false;
+ UpdateDialog();
+ SetVisible( false );
+ SetParent( (VPANEL) NULL );
+
+#ifdef _X360
+ SetDefaultSelections();
+ m_bShowBackButton = true;
+#endif
+ }
+#ifndef _X360
+ else if ( 0 == Q_stricmp( command, "resetstatsbutton" ) )
+ {
+ QueryBox *qb = new QueryBox( "#GameUI_Confirm", "#TF_ConfirmResetStats" );
+ if (qb != NULL)
+ {
+ qb->SetOKCommand(new KeyValues("DoResetStats") );
+ qb->AddActionSignalTarget(this);
+ qb->MoveToFront();
+ qb->DoModal();
+ }
+ }
+#endif
+ else if ( 0 == Q_stricmp( command, "nexttip" ) )
+ {
+ UpdateTip();
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Resets the dialog
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::Reset()
+{
+ m_aClassStats.RemoveAll();
+
+ SetDefaultSelections();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets all user-controllable dialog settings to default values
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::SetDefaultSelections()
+{
+ m_iSelectedClass = TF_CLASS_UNDEFINED;
+ m_statBarGraph[0] = TFSTAT_POINTSSCORED;
+ m_displayBarGraph[0]= SHOW_MAX;
+ m_statBarGraph[1] = TFSTAT_PLAYTIME;
+ m_displayBarGraph[1] = SHOW_TOTAL;
+
+ m_pBarChartComboBoxA->ActivateItemByRow( 0 );
+ m_pBarChartComboBoxB->ActivateItemByRow( 10 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the background image based on the current mode
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::UpdateMainBackground( void )
+{
+ if ( IsPC() )
+ {
+ m_pMainBackground = dynamic_cast<ImagePanel *>( FindChildByName( "MainBackground" ) );
+ if ( m_pMainBackground )
+ {
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() );
+
+ // determine if we're in widescreen or not and select the appropriate image
+ int screenWide, screenTall;
+ surface()->GetScreenSize( screenWide, screenTall );
+ float aspectRatio = (float)screenWide/(float)screenTall;
+ bool bIsWidescreen = aspectRatio >= 1.5999f;
+
+ if ( g_bIsReplayRewinding )
+ {
+ m_pMainBackground->SetImage( bIsWidescreen ? "../console/rewind_background_widescreen" : "../console/rewind_background" );
+ }
+ else if ( engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() )
+ {
+ m_pMainBackground->SetImage( bIsWidescreen ? "../console/replay_loading_widescreen" : "../console/replay_loading" );
+ }
+ else if ( pMatchDesc && pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) ) // Use match override if we have one
+ {
+ m_pMainBackground->SetImage( pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) );
+ }
+ else
+ {
+ m_pMainBackground->SetImage( bIsWidescreen ? "../console/background01_widescreen" : "../console/background01" );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Applies scheme settings
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::ApplySchemeSettings(vgui::IScheme *pScheme)
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetProportional( true );
+
+ if ( m_bEmbedded )
+ {
+ LoadControlSettings( "Resource/UI/StatSummary_Embedded.res" );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/StatSummary.res" );
+ }
+ m_bControlsLoaded = true;
+
+ // set the background image
+ UpdateMainBackground();
+
+ m_pMapInfoPanel = dynamic_cast< EditablePanel *>( FindChildByName( "MapInfo" ) );
+ m_vecLeaderboardEntries.RemoveAll();
+ if ( m_pMapInfoPanel )
+ {
+ for ( int i = 0; i < 10; ++ i )
+ {
+ vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( m_pMapInfoPanel, "LeaderboardEntry" );
+ pEntryUI->ApplySchemeSettings( pScheme );
+ pEntryUI->LoadControlSettings( "Resource/UI/LeaderboardEntry.res" );
+ m_vecLeaderboardEntries.AddToTail( pEntryUI );
+ }
+ }
+
+ // get the dimensions and position of a left-hand bar and a right-hand bar so we can do bar sizing later
+ Panel *pLHBar = m_pPlayerData->FindChildByName( "ClassBar1A" );
+ Panel *pRHBar = m_pPlayerData->FindChildByName( "ClassBar1B" );
+ if ( pLHBar && pRHBar )
+ {
+ int y;
+ pLHBar->GetBounds( m_xStartLHBar, y, m_iBarMaxWidth, m_iBarHeight );
+ pRHBar->GetBounds( m_xStartRHBar, y, m_iBarMaxWidth, m_iBarHeight );
+ }
+
+ // fill the combo box selections appropriately
+ InitBarChartComboBox( m_pBarChartComboBoxA );
+ InitBarChartComboBox( m_pBarChartComboBoxB );
+
+ // fill the class names in the class combo box
+ HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardSmall", true );
+ m_pClassComboBox->SetFont( hFont );
+ m_pClassComboBox->RemoveAll();
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "class", TF_CLASS_UNDEFINED );
+ m_pClassComboBox->AddItem( "#StatSummary_Label_AsAnyClass", pKeyValues );
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( iClass == TF_CLASS_CIVILIAN )
+ continue;
+ pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "class", iClass );
+ m_pClassComboBox->AddItem( g_aPlayerClassNames[iClass], pKeyValues );
+ }
+ m_pClassComboBox->ActivateItemByRow( 0 );
+
+ if ( m_pMapInfoPanel )
+ {
+ m_pContributedPanel = dynamic_cast< vgui::EditablePanel* >( m_pMapInfoPanel->FindChildByName( "ContributedLabel" ) );
+ }
+
+ SetDefaultSelections();
+ UpdateDialog();
+
+ if ( !m_bEmbedded )
+ {
+ SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::OnKeyCodePressed( KeyCode code )
+{
+ if ( IsX360() )
+ {
+ if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
+ {
+ OnCommand( "nexttip" ) ;
+ }
+ else if ( code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B )
+ {
+ OnCommand( "vguicancel" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets stats to use
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::SetStats( CUtlVector<ClassStats_t> &vecClassStats )
+{
+ m_aClassStats = vecClassStats;
+ if ( m_bControlsLoaded )
+ {
+ UpdateDialog();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the dialog
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::ClearMapLabel()
+{
+ SetDialogVariable( "maplabel", "" );
+ SetDialogVariable( "maptype", "" );
+
+ vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) );
+ if ( pLabel && pLabel->IsVisible() )
+ {
+ pLabel->SetVisible( false );
+ }
+
+ pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) );
+ if ( pLabel && pLabel->IsVisible() )
+ {
+ pLabel->SetVisible( false );
+ }
+
+ if ( m_pContributedPanel )
+ {
+ m_pContributedPanel->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::ShowMapInfo( bool bShowMapInfo, bool bIsMVM /*= false*/, bool bBackgroundOverride /*= false*/ )
+{
+ if ( m_pMainBackground )
+ {
+ m_pMainBackground->SetVisible( !bShowMapInfo );
+ }
+ m_pPlayerData->SetVisible( bIsMVM || !bShowMapInfo );
+ m_pNextTipButton->SetVisible( m_bInteractive && !bShowMapInfo );
+ m_pResetStatsButton->SetVisible( m_bInteractive && !bShowMapInfo );
+
+ if ( m_pMapInfoPanel )
+ {
+ m_pMapInfoPanel->SetVisible( bShowMapInfo );
+ vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" );
+ if ( pInfoBG )
+ {
+ pInfoBG->SetVisible( bShowMapInfo && !bIsMVM && !bBackgroundOverride );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::OnMapLoad( const char *pMapName )
+{
+ if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() )
+ return;
+
+ bool bWidescreenBackground = false;
+
+ bool bIsMVM = ( pMapName && !Q_strncmp( pMapName, "mvm_", 4 ) );
+ const char *pszBackgroundOverride = NULL;
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() );
+ if ( pMatchDesc )
+ {
+ int screenWide, screenTall;
+ surface()->GetScreenSize( screenWide, screenTall );
+ float aspectRatio = (float)screenWide/(float)screenTall;
+ bool bWideScreen = aspectRatio >= 1.5999f;
+
+ // Check if there's a widescreen override
+ if( bWideScreen )
+ {
+ pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( true );
+ if ( pszBackgroundOverride )
+ {
+ // Success! We're done
+ bWidescreenBackground = true;
+ }
+ }
+
+ if ( !bWideScreen && !pszBackgroundOverride )
+ {
+ pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( false );
+ }
+
+ }
+ else if ( bIsMVM )
+ {
+ // this will preserve the current behavior for non-matchmaking servers
+ pszBackgroundOverride = "mvm_background_map";
+ }
+
+ bool bIsCommunityMap = false;
+ const char *pAuthors = NULL;
+
+ const MapDef_t *pMapInfo = GetItemSchema()->GetMasterMapDefByName( pMapName );
+ if ( pMapInfo )
+ {
+ bIsCommunityMap = pMapInfo->IsCommunityMap();
+ pAuthors = pMapInfo->pszAuthorsLocKey;
+ }
+
+ ShowMapInfo( true, bIsMVM, ( pszBackgroundOverride != NULL ) );
+
+ m_xStartLeaderboard = 0;
+ m_yStartLeaderboard = 0;
+
+ // If we're loading a background map, don't display anything
+ // HACK: Client doesn't get gpGlobals->eLoadType, so just do string compare for now.
+ if ( Q_stristr( pMapName, "background") )
+ {
+ ClearMapLabel();
+ }
+ else
+ {
+ // set the map name in the UI
+ wchar_t wzMapName[255]=L"";
+ g_pVGuiLocalize->ConvertANSIToUnicode( GetMapDisplayName( pMapName ), wzMapName, sizeof( wzMapName ) );
+
+ SetDialogVariable( "maplabel", wzMapName );
+ SetDialogVariable( "maptype", g_pVGuiLocalize->Find( GetMapType( pMapName ) ) );
+
+ vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) );
+ if ( pLabel && !pLabel->IsVisible() )
+ {
+ pLabel->SetVisible( true );
+ }
+
+ pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) );
+ if ( pLabel && !pLabel->IsVisible() )
+ {
+ pLabel->SetVisible( true );
+ }
+
+ ImagePanel *pMapImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "MapImage" ) ) : NULL;
+ if ( pMapImage )
+ {
+ // load the map image (if it exists for the current map)
+ char szMapImage[ MAX_PATH ];
+ Q_snprintf( szMapImage, sizeof( szMapImage ), "VGUI/maps/menu_photos_%s", pMapName );
+ Q_strlower( szMapImage );
+
+ IMaterial *pMapMaterial = materials->FindMaterial( szMapImage, TEXTURE_GROUP_VGUI, false );
+ if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) && !pszBackgroundOverride )
+ {
+ // take off the vgui/ at the beginning when we set the image
+ Q_snprintf( szMapImage, sizeof( szMapImage ), "maps/menu_photos_%s", pMapName );
+ Q_strlower( szMapImage );
+ pMapImage->SetImage( szMapImage );
+ pMapImage->SetVisible( true );
+ }
+ else
+ {
+ pMapImage->SetVisible( false );
+ }
+ }
+
+ ImagePanel *pBackgroundImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "Background" ) ) : NULL;
+ if ( pBackgroundImage )
+ {
+ const char* pszBackgroundImage = pszBackgroundOverride ? pszBackgroundOverride : "stamp_background_map";
+
+ pBackgroundImage->SetImage( pszBackgroundImage );
+
+ // Resize to accomodate the background image coming in
+ if ( bWidescreenBackground )
+ {
+ pBackgroundImage->SetWide( GetWide() );
+ }
+ else
+ {
+ pBackgroundImage->SetWide( GetTall() * ( 4.f / 3.f ) );
+ }
+
+ }
+
+ if ( bIsMVM )
+ {
+ UpdateClassDetails( true );
+ m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" );
+ m_pMapInfoPanel->SetDialogVariable( "title", "" );
+ m_pMapInfoPanel->SetDialogVariable( "authors", "" );
+
+ FOR_EACH_VEC( m_vecLeaderboardEntries, i )
+ {
+ EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] );
+ if ( pContainer )
+ {
+ pContainer->SetVisible( false );
+ }
+ }
+ }
+ else
+ {
+ m_pLeaderboardTitle = NULL;
+ // add authors
+ if ( m_pMapInfoPanel )
+ {
+ if ( bIsCommunityMap )
+ {
+ m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_MapAuthors_Community_Title" ) );
+ m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" );
+ m_pMapInfoPanel->SetDialogVariable( "authors", g_pVGuiLocalize->Find( pAuthors ) );
+ m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "MapLeaderboardTitle" );
+ }
+ else
+ {
+ m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_DuelLeaderboard_Title" ) );
+ m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" );
+ m_pMapInfoPanel->SetDialogVariable( "authors", "" );
+ m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "Title" );
+ }
+ }
+ if ( m_pLeaderboardTitle )
+ {
+ m_pLeaderboardTitle->GetPos( m_xStartLeaderboard, m_yStartLeaderboard );
+ m_yStartLeaderboard += m_pLeaderboardTitle->GetTall();
+ }
+
+ // request leaderboard data
+ m_bShowingLeaderboard = true;
+ if ( bIsCommunityMap )
+ {
+ MapInfo_RefreshLeaderboard( pMapName );
+ }
+ else
+ {
+ Leaderboards_Refresh();
+ }
+ m_bLoadingCommunityMap = bIsCommunityMap;
+
+ if ( m_pContributedPanel && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() )
+ {
+ int iDonationAmount = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMapName );
+ m_pContributedPanel->SetVisible( iDonationAmount != 0 );
+ if ( iDonationAmount != 0 )
+ {
+ m_pContributedPanel->SetDialogVariable( "playername", steamapicontext->SteamFriends()->GetPersonaName() );
+ }
+ }
+
+ UpdateLeaderboard();
+ }
+ }
+}
+
+void CTFStatsSummaryPanel::UpdateLeaderboard()
+{
+ if ( m_pMapInfoPanel == NULL || steamapicontext == NULL || steamapicontext->SteamUserStats() == NULL || steamapicontext->SteamUser() == NULL )
+ return;
+
+ const int kMaxVisible_Supporters = 5;
+ const int kIdeallyNumVisible_Supporters = 3;
+ const int kMaxVisible_DuelWins = 10;
+ const int kIdeallyNumVisible_DuelWins = 5;
+
+ // retrieve scores
+ CUtlVector< LeaderboardEntry_t* > scores;
+ bool bVisible = true;
+ int iNumLeaderboardEntries = 0;
+ if ( m_bLoadingCommunityMap )
+ {
+ bVisible = MapInfo_GetLeaderboardInfo( engine->GetLevelName(), scores, iNumLeaderboardEntries, kIdeallyNumVisible_Supporters );
+ wchar_t wzNumEntriesString[256];
+ _snwprintf( wzNumEntriesString, ARRAYSIZE( wzNumEntriesString ), L"%i", iNumLeaderboardEntries );
+ wchar_t wzTitle[256];
+ g_pVGuiLocalize->ConstructString_safe( wzTitle, g_pVGuiLocalize->Find( "#TF_MapDonators_Title" ), 1, wzNumEntriesString );
+ m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", wzTitle );
+ }
+ else
+ {
+ bVisible = Leaderboards_GetDuelWins( scores, false );
+ if ( bVisible && scores.Count() < kIdeallyNumVisible_DuelWins )
+ {
+ bVisible = Leaderboards_GetDuelWins( scores, true ) && scores.Count() > 0;
+ }
+ // show old stats
+ m_pPlayerData->SetVisible( bVisible == false );
+ if ( m_pMapInfoPanel )
+ {
+ vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" );
+ if ( pInfoBG )
+ {
+ pInfoBG->SetVisible( bVisible );
+ }
+ }
+ }
+
+ const int kMaxVisible = m_bLoadingCommunityMap ? kMaxVisible_Supporters : kMaxVisible_DuelWins;
+
+ // try to show local player in relation to the people in the list
+ if ( bVisible && scores.Count() > 0 && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUserStats() )
+ {
+ int iLocalPlayerIdx = -1;
+ CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
+ FOR_EACH_VEC( scores, i )
+ {
+ const LeaderboardEntry_t *leaderboardEntry = scores[i];
+ if ( leaderboardEntry->m_steamIDUser == localSteamID )
+ {
+ iLocalPlayerIdx = i;
+ break;
+ }
+ }
+ // local player is in the list, but is outside the visible range
+ // so we want to move them to the last spot
+ // and move the closest person above them as well
+ if ( iLocalPlayerIdx >= kMaxVisible )
+ {
+ LeaderboardEntry_t *entryLocalPlayer = scores[iLocalPlayerIdx];
+ LeaderboardEntry_t *closestPlayer = scores[iLocalPlayerIdx - 1];
+ scores[kMaxVisible - 1] = entryLocalPlayer;
+ scores[kMaxVisible - 2] = closestPlayer;
+ }
+ }
+
+ // set avatars and names
+ int x = m_xStartLeaderboard;
+ int y = m_yStartLeaderboard;
+ FOR_EACH_VEC( m_vecLeaderboardEntries, i )
+ {
+ EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] );
+ if ( pContainer )
+ {
+ bool bIsEntryVisible = bVisible && i < scores.Count() && i < kMaxVisible;
+ pContainer->SetVisible( bIsEntryVisible );
+ pContainer->SetPos( x, y );
+ y += pContainer->GetTall();
+ if ( bIsEntryVisible )
+ {
+ const LeaderboardEntry_t *leaderboardEntry = scores[i];
+ const CSteamID &steamID = leaderboardEntry->m_steamIDUser;
+ pContainer->SetDialogVariable( "username", CFmtStr( "%d. %s - %d", leaderboardEntry->m_nGlobalRank, InventoryManager()->PersonaName_Get( steamID.GetAccountID() ), leaderboardEntry->m_nScore ) );
+ CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) );
+ if ( pAvatar )
+ {
+ pAvatar->SetShouldDrawFriendIcon( false );
+ pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 );
+ }
+ }
+ }
+ }
+
+ if ( m_pLeaderboardTitle )
+ {
+ bool bShowTitle = bVisible && scores.Count() > 0;
+ if ( m_pLeaderboardTitle->IsVisible() != bShowTitle )
+ {
+ m_pLeaderboardTitle->SetVisible( bShowTitle );
+ }
+ }
+
+ m_bShowingLeaderboard = bVisible;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the dialog
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::UpdateDialog()
+{
+ UpdateMainBackground();
+
+ if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() )
+ {
+ // hide all of the various panels for the other loadscreen modes
+ if ( IsPC() )
+ {
+ ClearMapLabel();
+
+ m_pPlayerData->SetVisible( false );
+ m_pNextTipButton->SetVisible( false );
+ m_pResetStatsButton->SetVisible( false );
+ m_pInteractiveHeaders->SetVisible( false );
+ m_pNonInteractiveHeaders->SetVisible( false );
+ m_pTipText->SetVisible( false );
+ m_pTipImage->SetVisible( false );
+
+ if ( m_pMapInfoPanel )
+ {
+ m_pMapInfoPanel->SetVisible( false );
+
+ vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" );
+ if ( pInfoBG )
+ {
+ pInfoBG->SetVisible( false );
+ }
+ }
+ }
+
+ return;
+ }
+
+ RandomSeed( Plat_MSTime() );
+
+ m_iTotalSpawns = 0;
+
+ // if we don't have stats for any class, add empty stat entries for them
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( iClass == TF_CLASS_CIVILIAN )
+ continue; // Ignore the civilian.
+
+ int j;
+ for ( j = 0; j < m_aClassStats.Count(); j++ )
+ {
+ if ( m_aClassStats[j].iPlayerClass == iClass )
+ {
+ m_iTotalSpawns += m_aClassStats[j].iNumberOfRounds;
+ break;
+ }
+ }
+ if ( j == m_aClassStats.Count() )
+ {
+ ClassStats_t stats;
+ stats.iPlayerClass = iClass;
+ m_aClassStats.AddToTail( stats );
+ }
+ }
+
+ ClearMapLabel();
+
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "nexttip", m_bShowBackButton );
+ m_pFooter->ShowButtonLabel( "back", m_bShowBackButton );
+ }
+#endif
+
+ // fill out bar charts
+ UpdateBarCharts();
+ // fill out class details
+ UpdateClassDetails();
+ // update the tip
+ UpdateTip();
+ // show or hide controls depending on if we're interactive or not
+ UpdateControls();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates bar charts
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::UpdateBarCharts()
+{
+ // sort the class stats by the selected stat for right-hand bar chart
+ m_aClassStats.Sort( &CTFStatsSummaryPanel::CompareClassStats );
+
+ // loop for left & right hand charts
+ for ( int iChart = 0; iChart < 2; iChart++ )
+ {
+ float flMax = 0;
+ for ( int i = 0; i < m_aClassStats.Count(); i++ )
+ {
+ // get max value of stat being charted so we know how to scale the graph
+ float flVal = GetDisplayValue( m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart] );
+ flMax = MAX( flVal, flMax );
+ }
+
+ // draw the bar chart value for each player class
+ // TODO: Fix up after the civilian becomes playable.
+ int iChartBar = 0;
+ for ( int i = 0; i < m_aClassStats.Count(); i++ )
+ {
+ int iClass = m_aClassStats[i].iPlayerClass;
+ if ( iClass == TF_CLASS_CIVILIAN )
+ {
+ continue;
+ }
+ if ( 0 == iChart )
+ {
+ // if this is the first chart, set the class label for each class
+ m_pPlayerData->SetDialogVariable( CFmtStr( "class%d", iChartBar+1 ), g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) );
+ }
+ // draw the bar for this class
+ DisplayBarValue( iChart, iChartBar++, m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart], flMax );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates class details
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::UpdateClassDetails( bool bIsMVM )
+{
+ vgui::Label *pTitle = assert_cast< vgui::Label* >( FindChildByName( "RecordsLabel1", true ) );
+ if ( pTitle )
+ {
+ pTitle->SetText( bIsMVM ? "#StatSummary_Label_BestMVMMoments" : "#StatSummary_Label_BestMoments" );
+ }
+
+ const wchar_t *wzWithClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" );
+ const wchar_t *wzWithoutClassFmt = L"%s1";
+
+ ClassDetails_t *pStatDetails = ( bIsMVM ? g_PerClassMVMStatDetails : g_PerClassStatDetails );
+ int nArraySize = ( bIsMVM ? ARRAYSIZE( g_PerClassMVMStatDetails ) : ARRAYSIZE( g_PerClassStatDetails ) );
+
+ // display the record for each stat
+ int iRow = 0;
+ for ( int i = 0; i < nArraySize; i++ )
+ {
+ TFStatType_t statType = pStatDetails[i].statType;
+
+ int iClass = TF_CLASS_UNDEFINED;
+ int iMaxVal = 0;
+
+ // if there is a selected class, and if this stat should not be shown for this class, skip this stat
+ if ( m_iSelectedClass != TF_CLASS_UNDEFINED && ( 0 == ( pStatDetails[i].iFlagsClass & MAKESTATFLAG( m_iSelectedClass ) ) ) )
+ continue;
+
+ if ( m_iSelectedClass == TF_CLASS_UNDEFINED )
+ {
+ // if showing best from any class, look through all player classes to determine the max value of this stat
+ for ( int j = 0; j < m_aClassStats.Count(); j++ )
+ {
+ RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max );
+ if ( pRoundStats->m_iStat[statType] > iMaxVal )
+ {
+ // remember max value and class that has max value
+ iMaxVal = pRoundStats->m_iStat[statType];
+ iClass = m_aClassStats[j].iPlayerClass;
+ }
+ }
+ }
+ else
+ {
+ // show best from selected class
+ iClass = m_iSelectedClass;
+ for ( int j = 0; j < m_aClassStats.Count(); j++ )
+ {
+ if ( m_aClassStats[j].iPlayerClass == iClass )
+ {
+ RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max );
+ iMaxVal = pRoundStats->m_iStat[statType];
+ break;
+ }
+ }
+ }
+
+ wchar_t wzStatNum[32];
+ wchar_t wzStatVal[128];
+ if ( TFSTAT_PLAYTIME == statType )
+ {
+ // playtime gets displayed as a time string
+ g_pVGuiLocalize->ConvertANSIToUnicode( FormatSeconds( iMaxVal ), wzStatNum, sizeof( wzStatNum ) );
+ }
+ else
+ {
+ // all other stats are just shown as a #
+ swprintf_s( wzStatNum, ARRAYSIZE( wzStatNum ), L"%d", iMaxVal );
+ }
+
+ if ( TF_CLASS_UNDEFINED == m_iSelectedClass && iMaxVal > 0 )
+ {
+ // if we are doing a cross-class view (no single selected class) and the max value is non-zero, show "# (as <class>)"
+ wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] );
+ g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithClassFmt, 2, wzStatNum, wzLocalizedClassName );
+ }
+ else
+ {
+ // just show the value
+ g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithoutClassFmt, 1, wzStatNum );
+ }
+
+ // set the label
+ m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), g_pVGuiLocalize->Find( pStatDetails[i].szResourceName ) );
+ // set the value
+ m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), wzStatVal );
+
+ iRow++;
+ }
+
+ // if there are any leftover rows for the selected class, fill out the remaining rows with blank labels and values
+ for ( ; iRow < 15; iRow ++ )
+ {
+ m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), "" );
+ m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), "" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the tip
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::UpdateTip()
+{
+ int iTipClass = TF_CLASS_UNDEFINED;
+
+ SetDialogVariable( "tiptext", g_TFTips.GetRandomTip( iTipClass ) );
+
+ if ( m_pTipImage )
+ {
+ if ( iTipClass > TF_CLASS_UNDEFINED && iTipClass <= TF_CLASS_ENGINEER )
+ {
+ m_pTipImage->SetVisible( true );
+ m_pTipImage->SetImage( g_pszTipsClassImages[iTipClass] );
+ }
+ else
+ {
+ m_pTipImage->SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shows or hides controls
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::UpdateControls()
+{
+ // show or hide controls depending on what mode we're in
+#ifndef _X360
+ bool bShowPlayerData = ( m_bInteractive || m_iTotalSpawns > 0 );
+#else
+ bool bShowPlayerData = ( m_bInteractive || m_bShowBackButton || m_iTotalSpawns > 0 );
+#endif
+ m_pPlayerData->SetVisible( bShowPlayerData );
+ m_pInteractiveHeaders->SetVisible( m_bInteractive );
+ m_pNonInteractiveHeaders->SetVisible( !m_bInteractive );
+ m_pTipText->SetVisible( bShowPlayerData );
+ m_pTipImage->SetVisible( bShowPlayerData );
+
+ if ( !IsX360() )
+ {
+ if ( !m_bInteractive )
+ {
+ char szTemp[128];
+
+ // update our non-interactive headers to match the current combo box selections
+ Label *pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelA" ) );
+ if ( pLabel && m_pBarChartComboBoxA )
+ {
+ m_pBarChartComboBoxA->GetItemText( m_pBarChartComboBoxA->GetActiveItem(), szTemp, sizeof( szTemp ) );
+ pLabel->SetText( szTemp );
+ }
+
+ pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelB" ) );
+ if ( pLabel && m_pBarChartComboBoxB )
+ {
+ m_pBarChartComboBoxB->GetItemText( m_pBarChartComboBoxB->GetActiveItem(), szTemp, sizeof( szTemp ) );
+ pLabel->SetText( szTemp );
+ }
+
+ pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "OverallRecordLabel" ) );
+ if ( pLabel && m_pClassComboBox )
+ {
+ m_pClassComboBox->GetItemText( m_pClassComboBox->GetActiveItem(), szTemp, sizeof( szTemp ) );
+ pLabel->SetText( szTemp );
+ }
+ }
+ }
+
+#ifndef _X360
+ m_pNextTipButton->SetVisible( m_bInteractive );
+ m_pResetStatsButton->SetVisible( m_bInteractive );
+ m_pCloseButton->SetVisible( m_bInteractive && !m_bEmbedded );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes a bar chart combo box
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::InitBarChartComboBox( ComboBox *pComboBox )
+{
+ struct BarChartComboInit_t
+ {
+ TFStatType_t statType;
+ StatDisplay_t statDisplay;
+ const char *szName;
+ };
+
+ BarChartComboInit_t initData[] =
+ {
+ { TFSTAT_POINTSSCORED, SHOW_MAX, "#StatSummary_StatTitle_MostPoints" },
+ { TFSTAT_POINTSSCORED, SHOW_AVG, "#StatSummary_StatTitle_AvgPoints" },
+ { TFSTAT_KILLS, SHOW_MAX, "#StatSummary_StatTitle_MostKills" },
+ { TFSTAT_KILLS, SHOW_AVG, "#StatSummary_StatTitle_AvgKills" },
+ { TFSTAT_CAPTURES, SHOW_MAX, "#StatSummary_StatTitle_MostCaptures" },
+ { TFSTAT_CAPTURES, SHOW_AVG, "#StatSummary_StatTitle_AvgCaptures" },
+ { TFSTAT_KILLASSISTS, SHOW_MAX, "#StatSummary_StatTitle_MostAssists" },
+ { TFSTAT_KILLASSISTS, SHOW_AVG, "#StatSummary_StatTitle_AvgAssists" },
+ { TFSTAT_DAMAGE, SHOW_MAX, "#StatSummary_StatTitle_MostDamage" },
+ { TFSTAT_DAMAGE, SHOW_AVG, "#StatSummary_StatTitle_AvgDamage" },
+ { TFSTAT_PLAYTIME, SHOW_TOTAL, "#StatSummary_StatTitle_TotalPlaytime" },
+ { TFSTAT_PLAYTIME, SHOW_MAX, "#StatSummary_StatTitle_LongestLife" },
+ };
+
+ // set the font
+ HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardVerySmall", true );
+ pComboBox->SetFont( hFont );
+ pComboBox->RemoveAll();
+ // add all the options to the combo box
+ for ( int i=0; i < ARRAYSIZE( initData ); i++ )
+ {
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "stattype", initData[i].statType );
+ pKeyValues->SetInt( "statdisplay", initData[i].statDisplay );
+ pComboBox->AddItem( g_pVGuiLocalize->Find( initData[i].szName ), pKeyValues );
+ }
+ pComboBox->SetNumberOfEditLines( ARRAYSIZE( initData ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Helper function that sets the specified dialog variable to
+// "<value> (as <localized class name>)"
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::SetValueAsClass( const char *pDialogVariable, int iValue, int iPlayerClass )
+{
+ if ( iValue > 0 )
+ {
+ wchar_t *wzScoreAsClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" );
+ wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iPlayerClass] );
+ wchar_t wzVal[16];
+ wchar_t wzMsg[128];
+ swprintf( wzVal, ARRAYSIZE( wzVal ), L"%d", iValue );
+ g_pVGuiLocalize->ConstructString_safe( wzMsg, wzScoreAsClassFmt, 2, wzVal, wzLocalizedClassName );
+ m_pPlayerData->SetDialogVariable( pDialogVariable, wzMsg );
+ }
+ else
+ {
+ m_pPlayerData->SetDialogVariable( pDialogVariable, "0" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the specified bar chart item to the specified value, in range 0->1
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::DisplayBarValue( int iChart, int iBar, ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay, float flMaxValue )
+{
+ const char *szControlSuffix = ( 0 == iChart ? "A" : "B" );
+ Panel *pBar = m_pPlayerData->FindChildByName( CFmtStr( "ClassBar%d%s", iBar+1, szControlSuffix ) );
+ Label *pLabel = dynamic_cast<Label*>( m_pPlayerData->FindChildByName( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ) ) );
+ if ( !pBar || !pLabel )
+ return;
+
+ // get the stat value
+ float flValue = GetDisplayValue( stats, statType, statDisplay );
+ // calculate the bar size to draw, in the range of 0.0->1.0
+ float flBarRange = SafeCalcFraction( flValue, flMaxValue );
+ // calculate the # of pixels of bar width to draw
+ int iBarWidth = MAX( (int) ( flBarRange * (float) m_iBarMaxWidth ), 1 );
+
+ // Get the text label to draw for this bar. For values of 0, draw nothing, to minimize clutter
+ const char *szLabel = ( flValue > 0 ? RenderValue( flValue, statType, statDisplay ) : "" );
+ // draw the label outside the bar if there's room
+ bool bLabelOutsideBar = true;
+ const int iLabelSpacing = 4;
+ HFont hFont = pLabel->GetFont();
+ int iLabelWidth = UTIL_ComputeStringWidth( hFont, szLabel );
+ if ( iBarWidth + iLabelWidth + iLabelSpacing > m_iBarMaxWidth )
+ {
+ // if there's not room outside the bar for the label, draw it inside the bar
+ bLabelOutsideBar = false;
+ }
+
+ int xBar,yBar,xLabel,yLabel;
+ pBar->GetPos( xBar,yBar );
+ pLabel->GetPos( xLabel,yLabel );
+
+ m_pPlayerData->SetDialogVariable( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ), szLabel );
+ if ( 1 == iChart )
+ {
+ // drawing code for RH bar chart
+ xBar = m_xStartRHBar;
+ pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight );
+ if ( bLabelOutsideBar )
+ {
+ pLabel->SetPos( xBar + iBarWidth + iLabelSpacing, yLabel );
+ }
+ else
+ {
+ pLabel->SetPos( xBar + iBarWidth - ( iLabelWidth + iLabelSpacing ), yLabel );
+ }
+ }
+ else
+ {
+ // drawing code for LH bar chart
+ xBar = m_xStartLHBar + m_iBarMaxWidth - iBarWidth;
+ pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight );
+ if ( bLabelOutsideBar )
+ {
+ pLabel->SetPos( xBar - ( iLabelWidth + iLabelSpacing ), yLabel );
+ }
+ else
+ {
+ pLabel->SetPos( xBar + iLabelSpacing, yLabel );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates a fraction and guards from divide by 0. (Returns 0 if
+// denominator is 0.)
+//-----------------------------------------------------------------------------
+float CTFStatsSummaryPanel::SafeCalcFraction( float flNumerator, float flDemoninator )
+{
+ if ( 0 == flDemoninator )
+ return 0;
+ return flNumerator / flDemoninator;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Formats # of seconds into a string
+//-----------------------------------------------------------------------------
+const char *FormatSeconds( int seconds )
+{
+ static char string[64];
+
+ int hours = 0;
+ int minutes = seconds / 60;
+
+ if ( minutes > 0 )
+ {
+ seconds -= (minutes * 60);
+ hours = minutes / 60;
+
+ if ( hours > 0 )
+ {
+ minutes -= (hours * 60);
+ }
+ }
+
+ if ( hours > 0 )
+ {
+ Q_snprintf( string, sizeof(string), "%2i:%02i:%02i", hours, minutes, seconds );
+ }
+ else
+ {
+ Q_snprintf( string, sizeof(string), "%02i:%02i", minutes, seconds );
+ }
+
+ return string;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static sort function that sorts in descending order by play time
+//-----------------------------------------------------------------------------
+int __cdecl CTFStatsSummaryPanel::CompareClassStats( const ClassStats_t *pStats0, const ClassStats_t *pStats1 )
+{
+ // sort stats first by right-hand bar graph
+ TFStatType_t statTypePrimary = GStatsSummaryPanel()->m_statBarGraph[1];
+ StatDisplay_t statDisplayPrimary = GStatsSummaryPanel()->m_displayBarGraph[1];
+ // then by left-hand bar graph
+ TFStatType_t statTypeSecondary = GStatsSummaryPanel()->m_statBarGraph[0];
+ StatDisplay_t statDisplaySecondary = GStatsSummaryPanel()->m_displayBarGraph[0];
+
+ float flValPrimary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypePrimary, statDisplayPrimary );
+ float flValPrimary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypePrimary, statDisplayPrimary );
+ float flValSecondary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypeSecondary, statDisplaySecondary );
+ float flValSecondary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypeSecondary, statDisplaySecondary );
+
+ // sort in descending order by primary stat value
+ if ( flValPrimary1 > flValPrimary0 )
+ return 1;
+ if ( flValPrimary1 < flValPrimary0 )
+ return -1;
+
+ // if primary stat values are equal, sort in descending order by secondary stat value
+ if ( flValSecondary1 > flValSecondary0 )
+ return 1;
+ if ( flValSecondary1 < flValSecondary0 )
+ return -1;
+
+ // if primary & secondary stats are equal, sort by class for consistent sort order
+ return ( pStats1->iPlayerClass - pStats0->iPlayerClass );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+
+ if ( m_pBarChartComboBoxA == pComboBox || m_pBarChartComboBoxB == pComboBox )
+ {
+ // a bar chart combo box changed, update the bar charts
+
+ KeyValues *pUserDataA = m_pBarChartComboBoxA->GetActiveItemUserData();
+ KeyValues *pUserDataB = m_pBarChartComboBoxB->GetActiveItemUserData();
+ if ( !pUserDataA || !pUserDataB )
+ return;
+ m_statBarGraph[0] = (TFStatType_t) pUserDataA->GetInt( "stattype" );
+ m_displayBarGraph[0] = (StatDisplay_t) pUserDataA->GetInt( "statdisplay" );
+ m_statBarGraph[1] = (TFStatType_t) pUserDataB->GetInt( "stattype" );
+ m_displayBarGraph[1] = (StatDisplay_t) pUserDataB->GetInt( "statdisplay" );
+ UpdateBarCharts();
+ }
+ else if ( m_pClassComboBox == pComboBox )
+ {
+ // the class selection combo box changed, update class details
+
+ KeyValues *pUserData = m_pClassComboBox->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ m_iSelectedClass = pUserData->GetInt( "class", TF_CLASS_UNDEFINED );
+
+ UpdateClassDetails();
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Command target handler called from reset stats confirmation query box
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::DoResetStats()
+{
+#ifndef _X360
+ // reset the stats
+ engine->ClientCmd( "resetplayerstats" );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the stat value for specified display type
+//-----------------------------------------------------------------------------
+float CTFStatsSummaryPanel::GetDisplayValue( ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay )
+{
+ switch ( statDisplay )
+ {
+ case SHOW_MAX:
+ return stats.max.m_iStat[statType];
+ break;
+ case SHOW_TOTAL:
+ return stats.accumulated.m_iStat[statType];
+ break;
+ case SHOW_AVG:
+ return SafeCalcFraction( stats.accumulated.m_iStat[statType], stats.iNumberOfRounds );
+ break;
+ default:
+ AssertOnce( false );
+ return 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets the text representation of this value
+//-----------------------------------------------------------------------------
+const char *CTFStatsSummaryPanel::RenderValue( float flValue, TFStatType_t statType, StatDisplay_t statDisplay )
+{
+ static char szValue[64];
+ if ( TFSTAT_PLAYTIME == statType )
+ {
+ // the playtime stat is shown in seconds
+ return FormatSeconds( (int) flValue );
+ }
+ else if ( SHOW_AVG == statDisplay )
+ {
+ // if it's an average, render as a float w/2 decimal places
+ Q_snprintf( szValue, ARRAYSIZE( szValue ), "%.2f", flValue );
+ }
+ else
+ {
+ // otherwise, render as an integer
+ Q_snprintf( szValue, ARRAYSIZE( szValue ), "%d", (int) flValue );
+ }
+
+ return szValue;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Event handler
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::FireGameEvent( IGameEvent *event )
+{
+ const char *pEventName = event->GetName();
+
+ // when we are changing levels and
+ if ( 0 == Q_strcmp( pEventName, "server_spawn" ) )
+ {
+ if ( !m_bInteractive )
+ {
+ const char *pMapName = event->GetString( "mapname" );
+ if ( pMapName )
+ {
+ OnMapLoad( pMapName );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when we are activated during level load
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::OnActivate()
+{
+ ClearMapLabel();
+
+ m_bShowingLeaderboard = false;
+ m_bLoadingCommunityMap = false;
+ ShowMapInfo( false );
+
+#ifdef _X360
+ m_bShowBackButton = false;
+#endif
+
+ UpdateDialog();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when we are deactivated at end of level load
+//-----------------------------------------------------------------------------
+void CTFStatsSummaryPanel::OnDeactivate()
+{
+ ClearMapLabel();
+}
+
+CON_COMMAND( showstatsdlg, "Shows the player stats dialog" )
+{
+#ifdef _DEBUG
+ GStatsSummaryPanel()->InvalidateLayout( false, true );
+#endif
+ GStatsSummaryPanel()->ShowModal();
+#ifdef _DEBUG
+ GStatsSummaryPanel()->OnMapLoad( "cp_coldfront" );
+#endif
+}
diff --git a/game/client/tf/vgui/tf_statsummary.h b/game/client/tf/vgui/tf_statsummary.h
new file mode 100644
index 0000000..eca7c72
--- /dev/null
+++ b/game/client/tf/vgui/tf_statsummary.h
@@ -0,0 +1,142 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#ifndef TF_STATSSUMMARY_H
+#define TF_STATSSUMMARY_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_hud_statpanel.h"
+#include "GameEventListener.h"
+
+struct ClassDetails_t
+{
+ TFStatType_t statType; // type of stat
+ uint iFlagsClass; // bit mask of classes to show this stat for
+ const char * szResourceName; // name of label resource (format "Most damage:"
+ const char * szAltResourceName; // name of alternative label resource "damage"
+};
+extern ClassDetails_t g_PerClassStatDetails[15];
+
+#define MAKESTATFLAG(x) ( 1 << x )
+#define ALL_CLASSES 0xFFFFFFFF
+
+class CTFStatsSummaryPanel : public vgui::EditablePanel, public CGameEventListener
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFStatsSummaryPanel, vgui::EditablePanel );
+
+public:
+ CTFStatsSummaryPanel();
+ CTFStatsSummaryPanel( vgui::Panel *parent );
+ ~CTFStatsSummaryPanel();
+
+ void Init( void );
+
+ virtual void ApplySchemeSettings(vgui::IScheme *pScheme);
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodePressed( KeyCode code );
+ virtual void PerformLayout();
+ virtual void OnThink();
+ void SetStats( CUtlVector<ClassStats_t> &vecClassStats );
+ void ShowModal();
+
+ void SetupForEmbedded( void );
+
+ void OnMapLoad( const char *pMapName );
+
+ virtual void FireGameEvent( IGameEvent *event );
+private:
+ MESSAGE_FUNC( OnActivate, "activate" );
+ MESSAGE_FUNC( OnDeactivate, "deactivate" );
+
+ enum StatDisplay_t
+ {
+ SHOW_MAX = 1,
+ SHOW_TOTAL,
+ SHOW_AVG
+ };
+
+ void Reset();
+ void SetDefaultSelections();
+ void UpdateDialog();
+ void UpdateBarCharts();
+ void UpdateClassDetails( bool bIsMVM = false );
+ void UpdateTip();
+ void UpdateControls();
+ void ClearMapLabel();
+ void ShowMapInfo( bool bShowMapInfo, bool bIsMVM = false, bool bBackgroundOverride = false );
+ void UpdateLeaderboard();
+ void InitBarChartComboBox( vgui::ComboBox *pComboBox );
+ void SetValueAsClass( const char *pDialogVariable, int iValue, int iPlayerClass );
+ void DisplayBarValue( int iChart, int iClass, ClassStats_t &stats, TFStatType_t statType, StatDisplay_t flags, float flMaxValue );
+ static float GetDisplayValue( ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay );
+ const char *RenderValue( float flValue, TFStatType_t statType, StatDisplay_t statDisplay );
+ static float SafeCalcFraction( float flNumerator, float flDemoninator );
+ static int __cdecl CompareClassStats( const ClassStats_t *pStats0, const ClassStats_t *pStats1 );
+ MESSAGE_FUNC( DoResetStats, "DoResetStats" );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+
+ vgui::EditablePanel *m_pPlayerData;
+
+ vgui::EditablePanel *m_pInteractiveHeaders;
+ vgui::EditablePanel *m_pNonInteractiveHeaders;
+ vgui::ComboBox *m_pBarChartComboBoxA;
+ vgui::ComboBox *m_pBarChartComboBoxB;
+ vgui::ComboBox *m_pClassComboBox;
+ CTFImagePanel *m_pTipImage;
+ vgui::Label *m_pTipText;
+ vgui::EditablePanel *m_pMapInfoPanel;
+ vgui::Panel *m_pLeaderboardTitle;
+
+ vgui::ImagePanel *m_pMainBackground;
+ void UpdateMainBackground( void );
+
+ vgui::EditablePanel *m_pContributedPanel;
+
+#ifdef _X360
+ CTFFooter *m_pFooter;
+#else
+ vgui::Button *m_pNextTipButton;
+ vgui::Button *m_pCloseButton;
+ vgui::Button *m_pResetStatsButton;
+#endif
+
+ bool m_bInteractive; // are we in interactive mode
+ bool m_bEmbedded; // are we embedded in a property sheet?
+ bool m_bControlsLoaded; // have we loaded controls yet
+ CUtlVector<ClassStats_t> m_aClassStats; // stats data
+ int m_xStartLHBar; // x min of bars in left hand bar chart
+ int m_xStartRHBar; // x min of bars in right hand bar chart
+ int m_iBarMaxWidth; // width of bars in bar charts
+ int m_iBarHeight; // height of bars in bar charts
+
+ int m_iSelectedClass; // what class is selected, if any
+ int m_iTotalSpawns; // how many spawns of all classes does this player have
+ TFStatType_t m_statBarGraph[2]; // what stat is displayed in the left hand and right hand bar graphs
+ StatDisplay_t m_displayBarGraph[2]; // the display type for the left hand and right hand bar graphs
+
+ bool m_bShowingLeaderboard;
+ bool m_bLoadingCommunityMap;
+ int m_xStartLeaderboard;
+ int m_yStartLeaderboard;
+ CUtlVector< vgui::EditablePanel* > m_vecLeaderboardEntries;
+
+#ifdef _X360
+ bool m_bShowBackButton;
+#endif
+};
+
+
+CTFStatsSummaryPanel *GStatsSummaryPanel();
+void DestroyStatsSummaryPanel();
+const char *FormatSeconds( int seconds );
+
+void UpdateStatSummaryPanels( CUtlVector<ClassStats_t> &vecClassStats );
+
+#endif // TF_STATSSUMMARY_H
diff --git a/game/client/tf/vgui/tf_teammenu.cpp b/game/client/tf/vgui/tf_teammenu.cpp
new file mode 100644
index 0000000..10be493
--- /dev/null
+++ b/game/client/tf/vgui/tf_teammenu.cpp
@@ -0,0 +1,901 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#include "cbase.h"
+
+#include <vgui_controls/Label.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui_controls/RichText.h>
+#include <vgui_controls/Frame.h>
+#include <vgui/IScheme.h>
+#include <game/client/iviewport.h>
+#include <vgui/IVGui.h>
+#include <KeyValues.h>
+#include <filesystem.h>
+#include <vgui_controls/AnimationController.h>
+#include "iclientmode.h"
+#include "clientmode_shared.h"
+#include "inputsystem/iinputsystem.h"
+
+#include "vguicenterprint.h"
+#include "tf_controls.h"
+#include "basemodelpanel.h"
+#include "tf_teammenu.h"
+#include <convar.h>
+#include "IGameUIFuncs.h" // for key bindings
+#include "hud.h" // for gEngfuncs
+#include "c_tf_player.h"
+#include "tf_gamerules.h"
+#include "c_team.h"
+#include "tf_hud_notification_panel.h"
+#include "iinput.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFTeamButton::CTFTeamButton( vgui::Panel *parent, const char *panelName ) : CExButton( parent, panelName, "" )
+{
+ m_szModelPanel[0] = '\0';
+ m_iTeam = TEAM_UNASSIGNED;
+ m_flHoverTimeToWait = -1;
+ m_flHoverTime = -1;
+ m_bMouseEntered = false;
+ m_bTeamDisabled = false;
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ Q_strncpy( m_szModelPanel, inResourceData->GetString( "associated_model", "" ), sizeof( m_szModelPanel ) );
+ m_iTeam = inResourceData->GetInt( "team", TEAM_UNASSIGNED );
+ m_flHoverTimeToWait = inResourceData->GetFloat( "hover", -1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetDefaultColor( GetFgColor(), Color( 0, 0, 0, 0 ) );
+ SetArmedColor( GetButtonFgColor(), Color( 0, 0, 0, 0 ) );
+ SetDepressedColor( GetButtonFgColor(), Color( 0, 0, 0, 0 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::SendAnimation( const char *pszAnimation )
+{
+ Panel *pParent = GetParent();
+ if ( pParent )
+ {
+ CModelPanel *pModel = dynamic_cast< CModelPanel* >( pParent->FindChildByName( m_szModelPanel ) );
+ if ( pModel )
+ {
+ KeyValues *kvParms = new KeyValues( "SetAnimation" );
+ if ( kvParms )
+ {
+ kvParms->SetString( "animation", pszAnimation );
+ PostMessage( pModel, kvParms );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::SetDefaultAnimation( const char *pszName )
+{
+ Panel *pParent = GetParent();
+ if ( pParent )
+ {
+ CModelPanel *pModel = dynamic_cast< CModelPanel* >( pParent->FindChildByName( m_szModelPanel ) );
+ if ( pModel )
+ {
+ pModel->SetDefaultAnimation( pszName );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFTeamButton::IsTeamFull()
+{
+ if ( TFGameRules() && TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
+ return false;
+
+ bool bRetVal = false;
+
+ if ( ( m_iTeam > TEAM_UNASSIGNED ) && GetParent() )
+ {
+ CTFTeamMenu *pTeamMenu = dynamic_cast< CTFTeamMenu* >( GetParent() );
+ if ( pTeamMenu )
+ {
+ bRetVal = ( m_iTeam == TF_TEAM_BLUE ) ? pTeamMenu->IsBlueTeamDisabled() : pTeamMenu->IsRedTeamDisabled();
+ }
+ }
+
+ return bRetVal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::OnCursorEntered()
+{
+ BaseClass::OnCursorEntered();
+
+ SetMouseEnteredState( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::OnCursorExited()
+{
+ BaseClass::OnCursorExited();
+
+ SetMouseEnteredState( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::SetMouseEnteredState( bool state )
+{
+ if ( state )
+ {
+ m_bMouseEntered = true;
+
+ if ( m_flHoverTimeToWait > 0 )
+ {
+ m_flHoverTime = gpGlobals->curtime + m_flHoverTimeToWait;
+ }
+ else
+ {
+ m_flHoverTime = -1;
+ }
+
+ if ( m_bTeamDisabled )
+ {
+ SendAnimation( "enter_disabled" );
+ }
+ else
+ {
+ SendAnimation( "enter_enabled" );
+ }
+ }
+ else
+ {
+ m_bMouseEntered = false;
+ m_flHoverTime = -1;
+
+ if ( m_bTeamDisabled )
+ {
+ SendAnimation( "exit_disabled" );
+ }
+ else
+ {
+ SendAnimation( "exit_enabled" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamButton::OnTick()
+{
+ if ( GetParent() && !GetParent()->IsVisible() )
+ return;
+
+ // check to see if our state has changed
+ bool bDisabled = IsTeamFull();
+
+ if ( bDisabled != m_bTeamDisabled )
+ {
+ m_bTeamDisabled = bDisabled;
+
+ if ( m_bMouseEntered )
+ {
+ // something has changed, so reset our state
+ SetMouseEnteredState( true );
+ }
+ else
+ {
+ // the mouse isn't currently over the button, but we should update the status
+ if ( m_bTeamDisabled )
+ {
+ SendAnimation( "idle_disabled" );
+ }
+ else
+ {
+ SendAnimation( "idle_enabled" );
+ }
+ }
+ }
+
+ if ( ( m_flHoverTime > 0 ) && ( m_flHoverTime < gpGlobals->curtime ) )
+ {
+ m_flHoverTime = -1;
+
+ if ( m_bTeamDisabled )
+ {
+ SendAnimation( "hover_disabled" );
+ }
+ else
+ {
+ SendAnimation( "hover_enabled" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFTeamMenu::CTFTeamMenu( IViewPort *pViewPort ) : CTeamMenu( pViewPort )
+{
+ SetMinimizeButtonVisible( false );
+ SetMaximizeButtonVisible( false );
+ SetCloseButtonVisible( false );
+ SetVisible( false );
+ SetKeyBoardInputEnabled( true );
+
+ m_iTeamMenuKey = BUTTON_CODE_INVALID;
+
+ m_pBlueTeamButton = new CTFTeamButton( this, "teambutton0" );
+ m_pRedTeamButton = new CTFTeamButton( this, "teambutton1" );
+ m_pAutoTeamButton = new CTFTeamButton( this, "teambutton2" );
+ m_pSpecTeamButton = new CTFTeamButton( this, "teambutton3" );
+ m_pSpecLabel = new CExLabel( this, "TeamMenuSpectate", "" );
+
+#ifdef _X360
+ m_pFooter = new CTFFooter( this, "Footer" );
+#else
+ m_pCancelButton = new CExButton( this, "CancelButton", "#TF_Cancel" );
+
+ m_pHighlanderLabel = new CExLabel( this, "HighlanderLabel", "" );
+ m_pHighlanderLabelShadow = new CExLabel( this, "HighlanderLabelShadow", "" );
+ m_pTeamsFullLabel = new CExLabel( this, "TeamsFullLabel", "" );
+ m_pTeamsFullLabelShadow = new CExLabel( this, "TeamsFullLabelShadow", "" );
+ m_pTeamsFullArrow = new CTFImagePanel( this, "TeamsFullArrow" );
+
+#endif
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
+
+ m_bRedDisabled = false;
+ m_bBlueDisabled = false;
+
+ if ( g_pInputSystem && ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/Teammenu_SC.res" );
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/Teammenu.res" );
+ SetMouseInputEnabled( true );
+ }
+
+ ListenForGameEvent( "server_spawn" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CTFTeamMenu::~CTFTeamMenu()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ LoadControlSettings( "Resource/UI/Teammenu_SC.res" );
+ m_pCancelHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "CancelHintIcon" ) );
+ m_pJoinAutoHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinAutoHintIcon" ) );
+ m_pJoinBluHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinBluHintIcon" ) );
+ m_pJoinRedHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinRedHintIcon" ) );
+ m_pJoinSpectatorsHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "JoinSpectatorsHintIcon" ) );
+
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/Teammenu.res" );
+
+ m_pCancelHintIcon = nullptr;
+ m_pJoinAutoHintIcon = m_pJoinRedHintIcon = m_pJoinBluHintIcon = m_pJoinSpectatorsHintIcon = nullptr;
+
+ SetMouseInputEnabled( true );
+ }
+
+ Update();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::ShowPanel( bool bShow )
+{
+ if ( BaseClass::IsVisible() == bShow )
+ return;
+
+ if ( !gameuifuncs || !gViewPortInterface || !engine )
+ return;
+
+ if ( bShow )
+ {
+ if ( !C_TFPlayer::GetLocalTFPlayer() )
+ return;
+
+ bool bDisallowChange = false;
+ if ( C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() >= FIRST_GAME_TEAM )
+ {
+ const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
+ if ( pMatchDesc && !pMatchDesc->m_params.m_bAllowTeamChange )
+ {
+ bDisallowChange = true;
+ }
+ }
+
+ if ( ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TFGameRules()->GetWinningTeam()
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_SPECTATOR
+ && C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() != TEAM_UNASSIGNED )
+ || TFGameRules()->State_Get() == GR_STATE_GAME_OVER
+ // [msmith] Don't allow the player to switch teams when in training.
+ || TFGameRules()->IsInTraining()
+ // or if they are coaching
+ || C_TFPlayer::GetLocalTFPlayer()->m_bIsCoaching
+ || bDisallowChange
+ )
+ {
+ SetVisible( false );
+
+ CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
+ if ( pNotifyPanel )
+ {
+ pNotifyPanel->SetupNotifyCustom( "#TF_CantChangeTeamNow", "ico_notify_flag_moving", C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() );
+ }
+
+ return;
+ }
+
+ extern void Coaching_CheckIfEligibleForCoaching();
+ Coaching_CheckIfEligibleForCoaching();
+
+ gViewPortInterface->ShowPanel( PANEL_CLASS_RED, false );
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, false );
+
+ engine->CheckPoint( "TeamMenu" );
+
+ // Force us to reload our scheme, in case Steam Controller stuff has changed.
+ InvalidateLayout( true, true );
+
+ Activate();
+
+ // get key bindings if shown
+ m_iTeamMenuKey = gameuifuncs->GetButtonCodeForBind( "changeteam" );
+ m_iScoreBoardKey = gameuifuncs->GetButtonCodeForBind( "showscores" );
+
+ switch ( C_TFPlayer::GetLocalTFPlayer()->GetTeamNumber() )
+ {
+ case TF_TEAM_BLUE:
+ if ( ::input->EnableJoystickMode() )
+ {
+ m_pBlueTeamButton->OnCursorEntered();
+ m_pBlueTeamButton->SetDefaultAnimation( "enter_enabled" );
+ }
+ GetFocusNavGroup().SetCurrentFocus( m_pBlueTeamButton->GetVPanel(), m_pBlueTeamButton->GetVPanel() );
+ break;
+
+ case TF_TEAM_RED:
+ if ( ::input->EnableJoystickMode() )
+ {
+ m_pRedTeamButton->OnCursorEntered();
+ m_pRedTeamButton->SetDefaultAnimation( "enter_enabled" );
+ }
+ GetFocusNavGroup().SetCurrentFocus( m_pRedTeamButton->GetVPanel(), m_pRedTeamButton->GetVPanel() );
+ break;
+
+ default:
+ if ( ::input->EnableJoystickMode() )
+ {
+ m_pAutoTeamButton->OnCursorEntered();
+ m_pAutoTeamButton->SetDefaultAnimation( "enter_enabled" );
+ }
+ GetFocusNavGroup().SetCurrentFocus( m_pAutoTeamButton->GetVPanel(), m_pAutoTeamButton->GetVPanel() );
+ break;
+ }
+
+ ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 );
+ }
+ else
+ {
+ SetVisible( false );
+
+ SetHighlanderTeamsFullPanels( false, true );
+
+ if ( ::input->EnableJoystickMode() )
+ {
+ // Close the door behind us
+ CTFTeamButton *pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorExited();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called to update the menu with new information
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::Update( void )
+{
+ BaseClass::Update();
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) )
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", true );
+ }
+#else
+ if ( m_pCancelButton )
+ {
+ m_pCancelButton->SetVisible( true );
+ if ( m_pCancelHintIcon )
+ {
+ m_pCancelHintIcon->SetVisible( true );
+ }
+ }
+#endif
+ }
+ else
+ {
+#ifdef _X360
+ if ( m_pFooter )
+ {
+ m_pFooter->ShowButtonLabel( "cancel", false );
+ }
+#else
+ if ( m_pCancelButton && m_pCancelButton->IsVisible() )
+ {
+ m_pCancelButton->SetVisible( false );
+ if ( m_pCancelHintIcon )
+ {
+ m_pCancelHintIcon->SetVisible( false );
+ }
+ }
+#endif
+ }
+}
+
+#ifdef _X360
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::Join_Team( const CCommand &args )
+{
+ if ( args.ArgC() > 1 )
+ {
+ char cmd[256];
+ Q_snprintf( cmd, sizeof( cmd ), "jointeam_nomenus %s", args.Arg( 1 ) );
+ OnCommand( cmd );
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: chooses and loads the text page to display that describes mapName map
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::LoadMapPage( const char *mapName )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::OnKeyCodePressed( KeyCode code )
+{
+ if ( ( m_iTeamMenuKey != BUTTON_CODE_INVALID && m_iTeamMenuKey == code ) ||
+ code == KEY_XBUTTON_BACK ||
+ code == KEY_XBUTTON_B ||
+ code == STEAMCONTROLLER_B )
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pLocalPlayer && ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) )
+ {
+ ShowPanel( false );
+ }
+ }
+ else if( code == KEY_SPACE || code == STEAMCONTROLLER_Y )
+ {
+ engine->ClientCmd( "jointeam auto" );
+
+ ShowPanel( false );
+ OnClose();
+ }
+ else if( code == KEY_XBUTTON_A || code == KEY_XBUTTON_RTRIGGER || code == STEAMCONTROLLER_A )
+ {
+ // select the active focus
+ if ( GetFocusNavGroup().GetCurrentFocus() )
+ {
+ ipanel()->SendMessage( GetFocusNavGroup().GetCurrentFocus()->GetVPanel(), new KeyValues( "PressButton" ), GetVPanel() );
+ }
+ }
+ else if( code == KEY_XBUTTON_RIGHT || code == KEY_XSTICK1_RIGHT || code == STEAMCONTROLLER_DPAD_RIGHT )
+ {
+ CTFTeamButton *pButton;
+
+ pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorExited();
+ GetFocusNavGroup().RequestFocusNext( pButton->GetVPanel() );
+ }
+ else
+ {
+ GetFocusNavGroup().RequestFocusNext( NULL );
+ }
+
+ pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorEntered();
+ }
+
+ ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 );
+ }
+ else if( code == KEY_XBUTTON_LEFT || code == KEY_XSTICK1_LEFT || code == STEAMCONTROLLER_DPAD_LEFT )
+ {
+ CTFTeamButton *pButton;
+
+ pButton = dynamic_cast< CTFTeamButton *> ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorExited();
+ GetFocusNavGroup().RequestFocusPrev( pButton->GetVPanel() );
+ }
+ else
+ {
+ GetFocusNavGroup().RequestFocusPrev( NULL );
+ }
+
+ pButton = dynamic_cast< CTFTeamButton * > ( GetFocusNavGroup().GetCurrentFocus() );
+ if ( pButton )
+ {
+ pButton->OnCursorEntered();
+ }
+
+ ActivateSelectIconHint( GetFocusNavGroup().GetCurrentFocus() ? GetFocusNavGroup().GetCurrentFocus()->GetTabPosition() : -1 );
+ }
+ else if ( m_iScoreBoardKey != BUTTON_CODE_INVALID && m_iScoreBoardKey == code )
+ {
+ gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, true );
+ gViewPortInterface->PostMessageToPanel( PANEL_SCOREBOARD, new KeyValues( "PollHideCode", "code", code ) );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when the user picks a team
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::OnCommand( const char *command )
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( Q_stricmp( command, "vguicancel" ) )
+ {
+ // we're selecting a team, so make sure it's not the team we're already on before sending to the server
+ if ( pLocalPlayer && ( Q_strstr( command, "jointeam " ) ) )
+ {
+ const char *pTeam = command + Q_strlen( "jointeam " );
+ int iTeam = TEAM_INVALID;
+
+ if ( Q_stricmp( pTeam, "spectate" ) == 0 )
+ {
+ iTeam = TEAM_SPECTATOR;
+ }
+ else if ( Q_stricmp( pTeam, "red" ) == 0 )
+ {
+ iTeam = TF_TEAM_RED;
+ }
+ else if ( Q_stricmp( pTeam, "blue" ) == 0 )
+ {
+ iTeam = TF_TEAM_BLUE;
+ }
+
+ if ( iTeam == TF_TEAM_RED && m_bRedDisabled )
+ {
+ return;
+ }
+
+ if ( iTeam == TF_TEAM_BLUE && m_bBlueDisabled )
+ {
+ return;
+ }
+
+ // are we selecting the team we're already on?
+ if ( pLocalPlayer->GetTeamNumber() != iTeam )
+ {
+ engine->ClientCmd( command );
+ }
+ }
+ else if ( pLocalPlayer && ( Q_strstr( command, "jointeam_nomenus " ) ) )
+ {
+ engine->ClientCmd( command );
+ }
+ }
+
+ BaseClass::OnCommand( command );
+ ShowPanel( false );
+ OnClose();
+}
+
+void CTFTeamMenu::OnClose()
+{
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ // Clear the HIDEHUD_HEALTH bit we hackily added. Turns out prediction
+ // was restoring these bits every frame. Unfortunately, prediction
+ // is off for karts which means the spell hud item would disappear if you
+ // brought up this menu and returned.
+ pLocalPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_HEALTH;
+ }
+
+ BaseClass::OnClose();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate the right selection hint icon, depending on the focus group number selected
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::ActivateSelectIconHint( int focus_group_number )
+{
+ if ( m_pJoinAutoHintIcon ) m_pJoinAutoHintIcon->SetVisible( false );
+ if ( m_pJoinBluHintIcon ) m_pJoinBluHintIcon->SetVisible( false );
+ if ( m_pJoinRedHintIcon ) m_pJoinRedHintIcon->SetVisible( false );
+ if ( m_pJoinSpectatorsHintIcon ) m_pJoinSpectatorsHintIcon->SetVisible( false );
+
+ CSCHintIcon* icon = nullptr;
+ switch ( focus_group_number )
+ {
+ case 1: icon = m_pJoinAutoHintIcon; break;
+ case 2: icon = m_pJoinSpectatorsHintIcon; break;
+ case 3: icon = m_pJoinBluHintIcon; break;
+ case 4: icon = m_pJoinRedHintIcon; break;
+ }
+
+ if ( icon )
+ {
+ icon->SetVisible( true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::SetHighlanderTeamsFullPanels( bool bTeamsFull, bool bForce /* = false */ )
+{
+ if ( m_pTeamsFullLabel )
+ {
+ if ( bForce || ( m_pTeamsFullLabel->IsVisible() != bTeamsFull ) )
+ {
+ m_pTeamsFullLabel->SetVisible( bTeamsFull );
+ }
+ }
+
+ if ( m_pTeamsFullLabelShadow )
+ {
+ if ( bForce || ( m_pTeamsFullLabelShadow->IsVisible() != bTeamsFull ) )
+ {
+ m_pTeamsFullLabelShadow->SetVisible( bTeamsFull );
+ }
+ }
+
+ if ( !mp_allowspectators.GetBool() )
+ {
+ // don't show the arrow if the server doesn't allow spectators
+ bTeamsFull = false;
+ }
+
+ if ( m_pTeamsFullArrow )
+ {
+ if ( bForce || ( m_pTeamsFullArrow->IsVisible() != bTeamsFull ) )
+ {
+ m_pTeamsFullArrow->SetVisible( bTeamsFull );
+
+ if ( bTeamsFull )
+ {
+ // turn on animation
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "TeamsFullArrowAnimate" );
+ }
+ else
+ {
+ // turn off animation
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "TeamsFullArrowAnimateEnd" );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Frame-based update
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::OnTick()
+{
+ // update the number of players on each team
+
+ // enable or disable buttons based on team limit
+
+ if ( !IsVisible() )
+ return;
+
+ C_Team *pRed = GetGlobalTeam( TF_TEAM_RED );
+ C_Team *pBlue = GetGlobalTeam( TF_TEAM_BLUE );
+
+ if ( !pRed || !pBlue )
+ return;
+
+ // set our team counts
+ SetDialogVariable( "bluecount", pBlue->Get_Number_Players() );
+ SetDialogVariable( "redcount", pRed->Get_Number_Players() );
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( !pLocalPlayer )
+ return;
+
+ CTFGameRules *pRules = TFGameRules();
+
+ if ( !pRules )
+ return;
+
+ bool bHighlander = pRules->IsInHighlanderMode();
+
+ if ( m_pHighlanderLabel )
+ {
+ if ( m_pHighlanderLabel->IsVisible() != bHighlander )
+ {
+ m_pHighlanderLabel->SetVisible( bHighlander );
+ }
+ }
+
+ if ( m_pHighlanderLabelShadow )
+ {
+ if ( m_pHighlanderLabelShadow->IsVisible() != bHighlander )
+ {
+ m_pHighlanderLabelShadow->SetVisible( bHighlander );
+ }
+ }
+
+ // check if teams are unbalanced
+ m_bRedDisabled = m_bBlueDisabled = false;
+
+ int iHeavyTeam, iLightTeam;
+
+ bool bUnbalanced = pRules->AreTeamsUnbalanced( iHeavyTeam, iLightTeam );
+
+ int iCurrentTeam = pLocalPlayer->GetTeamNumber();
+
+ if ( ( bUnbalanced && iHeavyTeam == TF_TEAM_RED ) ||
+ ( pRules->WouldChangeUnbalanceTeams( TF_TEAM_RED, iCurrentTeam ) ) ||
+ ( bHighlander && GetGlobalTeam( TF_TEAM_RED )->GetNumPlayers() >= TF_LAST_NORMAL_CLASS - 1 ) ||
+ ( pRules->IsMannVsMachineMode() && ( GetGlobalTeam( TF_TEAM_RED )->GetNumPlayers() >= kMVM_DefendersTeamSize ) ) )
+ {
+ m_bRedDisabled = true;
+ }
+
+ if ( ( bUnbalanced && iHeavyTeam == TF_TEAM_BLUE ) ||
+ ( pRules->WouldChangeUnbalanceTeams( TF_TEAM_BLUE, iCurrentTeam ) ) ||
+ ( bHighlander && GetGlobalTeam( TF_TEAM_BLUE )->GetNumPlayers() >= TF_LAST_NORMAL_CLASS - 1 ) ||
+ ( pRules->IsMannVsMachineMode() ) )
+ {
+ m_bBlueDisabled = true;
+ }
+
+ bool bTeamsFull = m_bRedDisabled && m_bBlueDisabled;
+ SetHighlanderTeamsFullPanels( bHighlander && bTeamsFull );
+
+ if ( m_pSpecTeamButton && m_pSpecLabel && m_pAutoTeamButton )
+ {
+ {
+ if ( mp_allowspectators.GetBool() )
+ {
+ if ( !m_pSpecTeamButton->IsVisible() )
+ {
+ m_pSpecTeamButton->SetVisible( true );
+ m_pSpecLabel->SetVisible( true );
+ }
+
+ if ( !m_pAutoTeamButton->IsVisible() )
+ {
+ m_pAutoTeamButton->SetVisible( true );
+ }
+ }
+ else
+ {
+ if ( m_pSpecTeamButton->IsVisible() )
+ {
+ m_pSpecTeamButton->SetVisible( false );
+ m_pSpecLabel->SetVisible( false );
+ }
+
+ if ( bHighlander )
+ {
+ if ( bTeamsFull )
+ {
+ if ( m_pAutoTeamButton->IsVisible() )
+ {
+ m_pAutoTeamButton->SetVisible( false );
+ }
+ }
+ else
+ {
+ if ( !m_pAutoTeamButton->IsVisible() )
+ {
+ m_pAutoTeamButton->SetVisible( true );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void CTFTeamMenu::OnThink()
+{
+ //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
+ }
+
+ BaseClass::OnThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTeamMenu::FireGameEvent( IGameEvent *event )
+{
+ // when we are changing levels
+ if ( FStrEq( event->GetName(), "server_spawn" ) )
+ {
+ SetHighlanderTeamsFullPanels( false, true );
+ }
+}
diff --git a/game/client/tf/vgui/tf_teammenu.h b/game/client/tf/vgui/tf_teammenu.h
new file mode 100644
index 0000000..9b5fca0
--- /dev/null
+++ b/game/client/tf/vgui/tf_teammenu.h
@@ -0,0 +1,136 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#ifndef TF_TEAMMENU_H
+#define TF_TEAMMENU_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tf_controls.h"
+#include "tf_imagepanel.h"
+#include <teammenu.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFTeamButton : public CExButton
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFTeamButton, CExButton );
+
+public:
+ CTFTeamButton( vgui::Panel *parent, const char *panelName );
+
+ void ApplySettings( KeyValues *inResourceData );
+ void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ void OnCursorExited();
+ void OnCursorEntered();
+
+ void OnTick( void );
+
+ void SetDefaultAnimation( const char *pszName );
+
+private:
+ bool IsTeamFull();
+ void SendAnimation( const char *pszAnimation );
+ void SetMouseEnteredState( bool state );
+
+private:
+ char m_szModelPanel[64]; // the panel we'll send messages to
+ int m_iTeam; // the team we're associated with (if any)
+
+ float m_flHoverTimeToWait; // length of time to wait before reporting a "hover" message (-1 = no hover)
+ float m_flHoverTime; // when should a "hover" message be sent?
+ bool m_bMouseEntered; // used to track when the mouse is over a button
+ bool m_bTeamDisabled; // used to keep track of whether our team is a valid team for selection
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Displays the team menu
+//-----------------------------------------------------------------------------
+class CTFTeamMenu : public CTeamMenu, public CGameEventListener
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFTeamMenu, CTeamMenu );
+
+public:
+ CTFTeamMenu( IViewPort *pViewPort );
+ ~CTFTeamMenu();
+
+ void Update();
+ void ShowPanel( bool bShow );
+
+#ifdef _X360
+ CON_COMMAND_MEMBER_F( CTFTeamMenu, "join_team", Join_Team, "Send a jointeam command", 0 );
+#endif
+
+
+ bool IsBlueTeamDisabled(){ return m_bBlueDisabled; }
+ bool IsRedTeamDisabled(){ return m_bRedDisabled; }
+
+ // IGameEventListener interface:
+ virtual void FireGameEvent( IGameEvent *event );
+
+protected:
+ virtual void ApplySchemeSettings(vgui::IScheme *pScheme);
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+
+ // command callbacks
+ virtual void OnCommand( const char *command );
+ virtual void OnClose();
+
+ virtual void LoadMapPage( const char *mapName );
+
+ virtual void OnTick( void );
+
+ virtual void OnThink() OVERRIDE;
+
+private:
+
+ void SetHighlanderTeamsFullPanels( bool bTeamsFull, bool bForce = false );
+ void ActivateSelectIconHint( int focus_group_number );
+
+private:
+
+ CTFTeamButton *m_pBlueTeamButton;
+ CTFTeamButton *m_pRedTeamButton;
+ CTFTeamButton *m_pAutoTeamButton;
+ CTFTeamButton *m_pSpecTeamButton;
+ CExLabel *m_pSpecLabel;
+
+#ifdef _X360
+ CTFFooter *m_pFooter;
+#else
+ CExButton *m_pCancelButton;
+
+ CExLabel *m_pHighlanderLabel;
+ CExLabel *m_pHighlanderLabelShadow;
+ CExLabel *m_pTeamsFullLabel;
+ CExLabel *m_pTeamsFullLabelShadow;
+ CTFImagePanel *m_pTeamsFullArrow;
+
+ CSCHintIcon *m_pCancelHintIcon;
+ CSCHintIcon *m_pJoinBluHintIcon;
+ CSCHintIcon *m_pJoinRedHintIcon;
+ CSCHintIcon *m_pJoinAutoHintIcon;
+ CSCHintIcon *m_pJoinSpectatorsHintIcon;
+
+#endif
+
+ bool m_bRedDisabled;
+ bool m_bBlueDisabled;
+
+
+private:
+ enum { NUM_TEAMS = 3 };
+
+ ButtonCode_t m_iTeamMenuKey;
+};
+
+#endif // TF_TEAMMENU_H
diff --git a/game/client/tf/vgui/tf_textwindow.cpp b/game/client/tf/vgui/tf_textwindow.cpp
new file mode 100644
index 0000000..2734860
--- /dev/null
+++ b/game/client/tf/vgui/tf_textwindow.cpp
@@ -0,0 +1,274 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "inputsystem/iinputsystem.h"
+#include "input.h"
+
+#include "tf_textwindow.h"
+#include <cdll_client_int.h>
+
+#include <vgui/IScheme.h>
+#include <vgui/ILocalize.h>
+#include <vgui/ISurface.h>
+#include <filesystem.h>
+#include <KeyValues.h>
+#include <convar.h>
+#include <vgui_controls/ImageList.h>
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/TextEntry.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/BuildGroup.h>
+#include <vgui_controls/ImagePanel.h>
+
+#include "tf_controls.h"
+#include "tf_shareddefs.h"
+
+#include "IGameUIFuncs.h" // for key bindings
+#include <igameresources.h>
+extern IGameUIFuncs *gameuifuncs; // for key binding details
+
+#include <game/client/iviewport.h>
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CTFTextWindow::CTFTextWindow( IViewPort *pViewPort ) : CTextWindow( pViewPort )
+{
+ m_pTFTextMessage = new CExRichText( this, "TFTextMessage" );
+
+ SetProportional( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CTFTextWindow::~CTFTextWindow()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::ApplySchemeSettings( IScheme *pScheme )
+{
+ Frame::ApplySchemeSettings( pScheme ); // purposely skipping the CTextWindow version
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ if ( m_bCustomSvrPage )
+ {
+ LoadControlSettings( "Resource/UI/TextWindowCustomServer_SC.res" );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/TextWindow_SC.res" );
+ }
+
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ if ( m_bCustomSvrPage )
+ {
+ LoadControlSettings( "Resource/UI/TextWindowCustomServer.res" );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/TextWindow.res" );
+ }
+ SetMouseInputEnabled( true );
+ }
+
+
+ if ( m_pHTMLMessage )
+ {
+ m_pHTMLMessage->SetBgColor( pScheme->GetColor( "HTMLBackground", Color( 255, 0, 0, 255 ) ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::Reset( void )
+{
+ Update();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::OnThink()
+{
+ //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
+ }
+
+ BaseClass::OnThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::SetData(KeyValues *data)
+{
+ m_bCustomSvrPage = data->GetBool( "customsvr" );
+ InvalidateLayout( false, true );
+ BaseClass::SetData( data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::Update()
+{
+ CExLabel *pTitle = dynamic_cast<CExLabel *>( FindChildByName( "TFMessageTitle" ) );
+ if ( pTitle )
+ {
+ pTitle->SetText( m_szTitle );
+ }
+
+ if ( m_pTFTextMessage )
+ {
+ m_pTFTextMessage->SetVisible( false );
+ }
+
+ BaseClass::Update();
+
+ Panel *pOK = FindChildByName( "ok" );
+ if ( pOK )
+ {
+ pOK->RequestFocus();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------
+void CTFTextWindow::SetVisible( bool state )
+{
+ BaseClass::SetVisible( state );
+
+ if ( state )
+ {
+ Panel *pOK = FindChildByName( "ok" );
+ if ( pOK )
+ {
+ pOK->RequestFocus();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: shows the text window
+//-----------------------------------------------------------------------------
+void CTFTextWindow::ShowPanel( bool bShow )
+{
+ if ( IsVisible() == bShow )
+ return;
+
+ // Force use to reevaluate our scheme, in case Steam Controller stuff has changed.
+ InvalidateLayout( true, true );
+
+ BaseClass::ShowPanel( bShow );
+
+ if ( m_pViewPort )
+ {
+ m_pViewPort->ShowBackGround( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::OnKeyCodePressed( KeyCode code )
+{
+ if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
+ {
+ OnCommand( "okay" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The background is painted elsewhere, so we should do nothing
+//-----------------------------------------------------------------------------
+void CTFTextWindow::PaintBackground()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::OnCommand( const char *command )
+{
+ BaseClass::OnCommand( command );
+
+ // Don't open up the mapinfo if it was a custom server html page
+ if ( !Q_strcmp( command, "okay" ) && !m_bCustomSvrPage )
+ {
+ m_pViewPort->ShowPanel( PANEL_MAPINFO, true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::ShowText( const char *text )
+{
+ ShowTitleLabel( true );
+
+ if ( m_pTFTextMessage )
+ {
+ m_pTFTextMessage->SetVisible( true );
+ m_pTFTextMessage->SetText( text );
+ m_pTFTextMessage->GotoTextStart();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::ShowURL( const char *URL, bool bAllowUserToDisable )
+{
+ ShowTitleLabel( false );
+ BaseClass::ShowURL( URL, bAllowUserToDisable );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::ShowFile( const char *filename )
+{
+ ShowTitleLabel( false ) ;
+ BaseClass::ShowFile( filename );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFTextWindow::ShowTitleLabel( bool show )
+{
+ CExLabel *pTitle = dynamic_cast<CExLabel *>( FindChildByName( "TFMessageTitle" ) );
+ if ( pTitle )
+ {
+ pTitle->SetVisible( show );
+ }
+}
diff --git a/game/client/tf/vgui/tf_textwindow.h b/game/client/tf/vgui/tf_textwindow.h
new file mode 100644
index 0000000..abb2958
--- /dev/null
+++ b/game/client/tf/vgui/tf_textwindow.h
@@ -0,0 +1,62 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TFTEXTWINDOW_H
+#define TFTEXTWINDOW_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include "vguitextwindow.h"
+#include "tf_controls.h"
+#include "IconPanel.h"
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose: displays the MOTD
+//-----------------------------------------------------------------------------
+
+class CTFTextWindow : public CTextWindow
+{
+private:
+ DECLARE_CLASS_SIMPLE( CTFTextWindow, CTextWindow );
+
+public:
+ CTFTextWindow( IViewPort *pViewPort );
+ virtual ~CTFTextWindow();
+
+ virtual void SetData(KeyValues *data);
+ virtual void Update();
+ virtual void Reset();
+ virtual void SetVisible(bool state);
+ virtual void ShowPanel( bool bShow );
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ShowFile( const char *filename );
+ virtual void ShowURL( const char *URL, bool bAllowUserToDisable = true );
+ virtual void ShowText( const char *text );
+ void ShowTitleLabel( bool show );
+ virtual void OnThink();
+
+ virtual GameActionSet_t GetPreferredActionSet() { return GAME_ACTION_SET_IN_GAME_HUD; }
+
+public:
+ virtual void PaintBackground();
+
+protected:
+ // vgui overrides
+ virtual void OnCommand( const char *command );
+
+private:
+ CExRichText *m_pTFTextMessage;
+ bool m_bCustomSvrPage;
+};
+
+
+#endif // TFTEXTWINDOW_H
diff --git a/game/client/tf/vgui/tf_training_ui.cpp b/game/client/tf/vgui/tf_training_ui.cpp
new file mode 100644
index 0000000..808c423
--- /dev/null
+++ b/game/client/tf/vgui/tf_training_ui.cpp
@@ -0,0 +1,2205 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include <filesystem.h>
+#include "ienginevgui.h"
+#include "tf_gcmessages.h"
+#include "tf_mouseforwardingpanel.h"
+#include "gc_clientsystem.h"
+#include "c_tf_gamestats.h"
+#include "tf_hud_mainmenuoverride.h"
+#include "tf_gamerules.h"
+#include "econ/confirm_dialog.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//------------------------------------------------------------------------------------------------------
+
+#define TRAINING_DIALOG_NAME "TrainingDialog"
+#define TRAINING_PROGRESS_FILE "trainingprogress.txt"
+
+//------------------------------------------------------------------------------------------------------
+
+#ifdef _DEBUG
+#define PRINT_KEY_VALUES( kv_ ) { CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER); kv_->RecursiveSaveToFile( buf, 0 ); ConMsg( "---\n%s\n---\n", buf.String() ); }
+#else
+#define PRINT_KEY_VALUES( kv_ ) { }
+#endif
+
+//------------------------------------------------------------------------------------------------------
+
+enum GameMode_t // Supported game modes for offline practice
+{
+ MODE_INVALID = -1,
+
+ MODE_CP,
+ MODE_KOTH,
+ MODE_PL,
+
+ NUM_GAME_MODES
+};
+
+static const char *gs_pGameModeTokens[ NUM_GAME_MODES ] = {
+ "#Gametype_CP",
+ "#Gametype_Koth",
+ "#Gametype_Escort",
+};
+
+//------------------------------------------------------------------------------------------------------
+
+ConVar cl_training_completed_with_classes( "cl_training_completed_with_classes", "0", FCVAR_ARCHIVE, "Bitfield representing what classes have been used to complete training." );
+
+bool Training_TrainingProgressFileExists()
+{
+ const char *pFilename = TRAINING_PROGRESS_FILE;
+ return g_pFullFileSystem->FileExists( pFilename, NULL );
+}
+
+//------------------------------------------------------------------------------------------------------
+
+static int Training_GetClassProgress( int iClass ) // Returns a percent, in the range [0,100]
+{
+ Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass <= TF_LAST_NORMAL_CLASS );
+
+ const int nDefaultResult = iClass == TF_CLASS_SOLDIER ? 0 : -1;
+
+ KeyValuesAD pTrainingProgressData( "TrainingProgress" );
+ if ( !pTrainingProgressData )
+ {
+ Warning( "Failed to save training progress!\n" );
+ AssertMsg( 0, "Failed to save training progress!\n" );
+ return nDefaultResult;
+ }
+
+ const char *pFilename = TRAINING_PROGRESS_FILE;
+ if ( !pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) )
+ return nDefaultResult;
+
+ const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ];
+ KeyValues *pClassSubKey = pTrainingProgressData->FindKey( pClassName );
+ if ( !pClassSubKey )
+ return nDefaultResult;
+
+ return pClassSubKey->GetInt( "progress", nDefaultResult );
+}
+
+static void Training_GetProgress( int pClass[TF_CLASS_COUNT] ) // Returns a percent, in the range [0,100]
+{
+ KeyValuesAD pTrainingProgressData( "TrainingProgress" );
+ const char *pFilename = TRAINING_PROGRESS_FILE;
+
+ bool bLoadedFileOk = pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" );
+
+ for ( int i = 0; i < TF_CLASS_COUNT; ++i )
+ {
+ const int iClass = i;
+ const int nDefaultResult = ( bLoadedFileOk && iClass == TF_CLASS_SOLDIER ) ? 0 : -1;
+
+ const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ];
+ KeyValues *pClassSubKey = pTrainingProgressData->FindKey( pClassName );
+ if ( !pClassSubKey )
+ {
+ pClass[ i ] = nDefaultResult;
+ continue;
+ }
+
+ pClass[ i ] = pClassSubKey->GetInt( "progress", nDefaultResult );
+ }
+}
+
+KeyValues *Training_LoadProgressFile()
+{
+ KeyValues *pTrainingProgressData = new KeyValues( "TrainingProgress" );
+ if ( !pTrainingProgressData )
+ {
+ Warning( "Failed to save training progress!\n" );
+ AssertMsg( 0, "Failed to save training progress!\n" );
+ return NULL;
+ }
+
+ // Attempt to load any existing progress from disk
+ const char *pFilename = TRAINING_PROGRESS_FILE;
+ if ( !pTrainingProgressData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) )
+ {
+ // File didn't exist - create from defaults
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i )
+ {
+ KeyValues *pClassSubKey = new KeyValues( g_aPlayerClassNames_NonLocalized[ i ] );
+ if ( !pClassSubKey )
+ continue;
+
+ pClassSubKey->SetInt( "progress", -1 ); // -1 means they haven't beat anything
+ pTrainingProgressData->AddSubKey( pClassSubKey );
+ }
+ }
+
+ return pTrainingProgressData;
+}
+
+void Training_SaveProgress( KeyValues *pTrainingProgressData )
+{
+ const char *pFilename = TRAINING_PROGRESS_FILE;
+ if ( !pTrainingProgressData->SaveToFile( g_pFullFileSystem, pFilename, "MOD" ) )
+ {
+ Warning( "Failed to save progress!\n" );
+ AssertMsg( 0, "Failed to save progress!" );
+ }
+}
+
+KeyValues *Training_FindClassData( KeyValues *pTrainingProgressData, int iClass )
+{
+ const char *pClassName = g_aPlayerClassNames_NonLocalized[ iClass ];
+ return pTrainingProgressData->FindKey( pClassName );
+}
+
+void Training_SaveProgress( int pProgress[ TF_CLASS_COUNT ] )
+{
+ KeyValues *pTrainingProgressData = Training_LoadProgressFile();
+ if ( !pTrainingProgressData )
+ return;
+
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i )
+ {
+ KeyValues *pClassSubKey = Training_FindClassData( pTrainingProgressData, i );
+ if ( !pClassSubKey )
+ {
+ AssertMsg( 0, "All classes should have been created on load if they didn't exist - this should not happen!" );
+ continue;
+ }
+
+ Assert( pProgress[ i ] >= -1 );
+ pClassSubKey->SetInt( "progress", pProgress[ i ] );
+ }
+
+ Training_SaveProgress( pTrainingProgressData );
+}
+
+void Training_MarkClassComplete( int iClass, int iStage )
+{
+ Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS );
+ Assert( iStage >= 0 );
+
+ KeyValues *pTrainingProgressData = Training_LoadProgressFile();
+ if ( !pTrainingProgressData )
+ return;
+
+ // Find the data for the corresponding class
+ KeyValues *pClassSubKey = Training_FindClassData( pTrainingProgressData, iClass );
+ if ( pClassSubKey )
+ {
+ pClassSubKey->SetInt( "progress", iStage );
+ }
+ else
+ {
+ Warning( "Failed to load data for class %s!\n", g_aPlayerClassNames_NonLocalized[ iClass ] );
+ }
+
+ // Unlock next class if necessary
+ const int iLastTrainingClass = TF_CLASS_ENGINEER;
+ if ( iClass != iLastTrainingClass )
+ {
+ const int aNextClasses[ TF_CLASS_COUNT ] = {
+ -1, // TF_CLASS_UNDEFINED
+ -1, // TF_CLASS_SCOUT
+ -1, // TF_CLASS_SNIPER
+ TF_CLASS_DEMOMAN, // TF_CLASS_SOLDIER
+ TF_CLASS_SPY, // TF_CLASS_DEMOMAN
+ -1, // TF_CLASS_MEDIC
+ -1, // TF_CLASS_HEAVYWEAPONS
+ -1, // TF_CLASS_PYRO
+ TF_CLASS_ENGINEER, // TF_CLASS_SPY
+ -1, // TF_CLASS_ENGINEER
+ };
+ const int aUnlockRequirements[ TF_CLASS_COUNT ] = {
+ -1, // TF_CLASS_UNDEFINED
+ -1, // TF_CLASS_SCOUT
+ -1, // TF_CLASS_SNIPER
+ 2, // TF_CLASS_SOLDIER - must beat 2 stages to complete soldier training
+ 1, // TF_CLASS_DEMOMAN
+ -1, // TF_CLASS_MEDIC
+ -1, // TF_CLASS_HEAVYWEAPONS
+ -1, // TF_CLASS_PYRO
+ 1, // TF_CLASS_SPY
+ -1, // TF_CLASS_ENGINEER
+ };
+ const int iNextClass = aNextClasses[ iClass ];
+ const bool bCurrentClassCompleted = iStage >= aUnlockRequirements[ iClass ];
+ if ( iNextClass >= TF_FIRST_NORMAL_CLASS && bCurrentClassCompleted )
+ {
+ // Find the data for the given class and unlock it
+ KeyValues *pNextClassData = pTrainingProgressData->FindKey( g_aPlayerClassNames_NonLocalized[ iNextClass ] );
+ if ( pNextClassData )
+ {
+ pNextClassData->SetInt( "progress", 0 );
+ }
+ else
+ {
+ AssertMsg( 0, "This class data should have been filled out above" );
+ }
+ }
+ }
+
+ // Attempt to save
+ Training_SaveProgress( pTrainingProgressData );
+
+ // Free
+ pTrainingProgressData->deleteThis();
+}
+
+static ConVar training_map_video( "training_map_video", "", 0, "Video to show for training" );
+
+void CL_Training_LevelShutdown()
+{
+ training_map_video.Revert();
+}
+
+int Training_GetNumCoursesForClass( int iClass )
+{
+ static int s_aClassCourses[ TF_CLASS_COUNT ] = {
+ 0, // TF_CLASS_UNDEFINED
+ 0, // TF_CLASS_SCOUT
+ 0, // TF_CLASS_SNIPER
+ 2, // TF_CLASS_SOLDIER
+ 1, // TF_CLASS_DEMOMAN
+ 0, // TF_CLASS_MEDIC
+ 0, // TF_CLASS_HEAVYWEAPONS
+ 0, // TF_CLASS_PYRO
+ 1, // TF_CLASS_SPY
+ 1, // TF_CLASS_ENGINEER
+ };
+
+ AssertMsg( iClass >= 0 && iClass < TF_CLASS_COUNT, "Training_GetNumCoursesForClass(): Class out of range!" );
+ return s_aClassCourses[ iClass ];
+}
+
+int Training_GetNumCourses()
+{
+ static bool s_bComputed = false;
+ static int s_nTotal = 0;
+
+ if ( !s_bComputed )
+ {
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i )
+ {
+ s_nTotal += Training_GetNumCoursesForClass( i );
+ }
+ s_bComputed = true;
+ }
+
+ AssertMsg( s_nTotal == 5, "Number of total courses is incorrect - should be soldier (2) + demo (1) + spy (1) + engy (1)" );
+
+ return s_nTotal;
+}
+
+int Training_GetProgressCount()
+{
+ int aProgress[ TF_CLASS_COUNT ];
+ Training_GetProgress( aProgress );
+
+ int nTotalProgress = 0;
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i )
+ {
+ int nClassProgress = Training_GetClassProgress( i );
+ if ( nClassProgress > 0 )
+ {
+ nTotalProgress += nClassProgress;
+ }
+ }
+
+ return nTotalProgress;
+}
+
+bool Training_IsComplete()
+{
+ return Training_GetProgressCount() == Training_GetNumCourses();
+}
+
+void Training_Init()
+{
+ // If the progress file already exists, early out as we only do conversation from the old system to the new here.
+ if ( Training_TrainingProgressFileExists() )
+ return;
+
+ int aProgress[ TF_CLASS_COUNT ];
+
+ int fProgressOld = cl_training_completed_with_classes.GetInt();
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i )
+ {
+ if ( ( fProgressOld & ( 1 << i ) ) != 0 )
+ {
+ aProgress[ i ] = 1;
+ }
+ else
+ {
+ aProgress[ i ] = -1;
+ }
+ }
+
+ const int TRAINING_CLASS_ATTACK_DEFEND = 15;
+
+ // Add an explicit check for attack/defend
+ if ( ( fProgressOld & ( 1 << TRAINING_CLASS_ATTACK_DEFEND ) ) != 0 )
+ {
+ aProgress[ TF_CLASS_SOLDIER ] = 2;
+ }
+
+ // Soldier should always be at least 0
+ aProgress[ TF_CLASS_SOLDIER ] = MAX( aProgress[ TF_CLASS_SOLDIER ], 0 );
+
+ // Save a file with the given progress settings
+ Training_SaveProgress( aProgress );
+}
+
+//------------------------------------------------------------------------------------------------------
+
+Panel *FindAncestorByName( Panel *pChild, const char *pName )
+{
+ if ( !pChild )
+ return NULL;
+
+ Panel *pCurrent = pChild->GetParent();
+ while ( pCurrent )
+ {
+ if ( FStrEq( pCurrent->GetName(), pName ) )
+ return pCurrent;
+
+ pCurrent = pCurrent->GetParent();
+ }
+
+ return NULL;
+}
+
+CExButton *SetupButtonActionSignalTarget( Panel *pParent, const char *pButtonName, const char *pCommand = NULL )
+{
+ CExButton *pButton = dynamic_cast< CExButton * >( pParent->FindChildByName( pButtonName ) );
+ EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( pParent, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog );
+
+ if ( pButton && pTrainingDialog )
+ {
+ if ( pCommand )
+ {
+ pButton->SetCommand( pCommand );
+ }
+
+ pButton->AddActionSignalTarget( pTrainingDialog );
+ }
+
+ return pButton;
+}
+
+//------------------------------------------------------------------------------------------------------
+
+//
+// Sets dialog title/subtitle and sets up cancel/back buttons
+//
+class CTrainingBasePanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTrainingBasePanel, EditablePanel );
+public:
+ CTrainingBasePanel( Panel *pParent, const char *pName )
+ : EditablePanel( pParent, pName ),
+ m_pPrevPagePanel( NULL )
+ {
+ }
+
+ virtual void ApplySettings( KeyValues *pInResourceData )
+ {
+ BaseClass::ApplySettings( pInResourceData );
+
+ m_strTitleToken = pInResourceData->GetString( "TrainingTitle", NULL );
+ m_strSubTitleToken = pInResourceData->GetString( "TrainingSubTitle", NULL );
+ }
+
+ inline bool FindCharInWideString( const wchar_t *pStr, wchar_t c )
+ {
+ if ( !pStr )
+ return false;
+
+ const int nLen = V_wcslen( pStr );
+ for ( int i = 0; i < nLen; ++i )
+ {
+ if ( pStr[ i ] == c )
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( this, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog );
+ if ( pTrainingDialog )
+ {
+ const wchar_t *pTitleString = g_pVGuiLocalize->Find( m_strTitleToken.Get() );
+ if ( FindCharInWideString( pTitleString, L'%' ) )
+ {
+ KeyValues *pTitleFormatData = GetTitleFormatData(); AssertMsg( pTitleFormatData, "Should get valid data here." );
+ if ( pTitleFormatData )
+ {
+ wchar_t wszTitle[ 1024 ];
+ g_pVGuiLocalize->ConstructString_safe( wszTitle, m_strTitleToken.Get(), pTitleFormatData );
+ pTitleFormatData->deleteThis();
+
+ pTrainingDialog->SetDialogVariable( "title", wszTitle );
+ }
+ }
+ else
+ {
+ pTrainingDialog->SetDialogVariable( "title", g_pVGuiLocalize->Find( m_strTitleToken.Get() ) );
+ }
+
+ pTrainingDialog->SetDialogVariable( "subtitle", g_pVGuiLocalize->Find( m_strSubTitleToken ) );
+ }
+ }
+
+ virtual void OnCommand( const char *pCommand )
+ {
+ if ( FStrEq( pCommand, "goprev" ) )
+ {
+ GoPrev();
+ }
+ else if ( FStrEq( pCommand, "gonext" ) )
+ {
+ GoNext();
+ }
+ else
+ {
+ BaseClass::OnCommand( pCommand );
+ }
+ }
+
+ virtual void OnKeyCodePressed( KeyCode nCode )
+ {
+ ButtonCode_t nButtonCode = GetBaseButtonCode( nCode );
+
+ if ( nCode == KEY_SPACE || nCode == KEY_ENTER || nCode == KEY_XBUTTON_A || nCode == STEAMCONTROLLER_A )
+ {
+ Go();
+ }
+ else if ( nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_LEFT ||
+ nButtonCode == KEY_LEFT )
+ {
+ GoPrev();
+ }
+ else if ( nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_RIGHT ||
+ nButtonCode == KEY_RIGHT )
+ {
+ GoNext();
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( nCode );
+ }
+ }
+
+ void Go()
+ {
+ EditablePanel *pTrainingDialog = static_cast< EditablePanel * >( FindAncestorByName( this, TRAINING_DIALOG_NAME ) ); Assert( pTrainingDialog );
+ if ( pTrainingDialog )
+ {
+ const char *pGoCommand = GetGoCommand();
+ if ( pGoCommand )
+ {
+ pTrainingDialog->OnCommand( pGoCommand );
+ }
+ }
+ }
+
+ virtual void GoPrev()
+ {
+ }
+
+ virtual void GoNext()
+ {
+ }
+
+ virtual void OnBackPressed()
+ {
+ }
+
+ virtual KeyValues *GetTitleFormatData() const
+ {
+ return NULL;
+ }
+
+ virtual const char *GetGoCommand() const
+ {
+ return NULL;
+ }
+
+ void SetPrevPage( CTrainingBasePanel *pPanel )
+ {
+ m_pPrevPagePanel = pPanel;
+ }
+
+ CTrainingBasePanel *GetPrevPage()
+ {
+ return m_pPrevPagePanel;
+ }
+
+ virtual bool IsFirstPage() const
+ {
+ return false;
+ }
+
+ virtual bool ShouldShowGradient() const
+ {
+ return false;
+ }
+
+protected:
+ CUtlString m_strTitleToken;
+ CUtlString m_strSubTitleToken;
+ CTrainingBasePanel *m_pPrevPagePanel;
+};
+
+//------------------------------------------------------------------------------------------------------
+
+class CTrainingBaseCarouselPanel : public CTrainingBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CTrainingBaseCarouselPanel, CTrainingBasePanel );
+public:
+ CTrainingBaseCarouselPanel( Panel *pParent, const char *pName )
+ : CTrainingBasePanel( pParent, pName ),
+ m_iPage( 0 )
+ {
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ CFmtStr fmtCurPageLabelText( "%i/%i", m_iPage + 1, GetNumPages() );
+ SetDialogVariable( "curpage", fmtCurPageLabelText.Access() );
+
+ const int nNumPages = GetNumPages();
+
+ // Set visibility on buttons and current page based on the number of pages.
+ CExLabel *pCurPageLabel = dynamic_cast< CExLabel * >( FindChildByName( "CurPageLabel" ) );
+ if ( pCurPageLabel )
+ {
+ pCurPageLabel->SetVisible( nNumPages > 1 );
+ }
+
+ const char *pNavButtonNames[2] = { "PrevButton", "NextButton" };
+ for ( int i = 0; i < 2; ++i )
+ {
+ CExButton *pCurButton = dynamic_cast< CExButton * >( FindChildByName( pNavButtonNames[ i ] ) );
+ if ( !pCurButton )
+ continue;
+ pCurButton->SetVisible( nNumPages > 1 );
+ }
+ }
+
+ virtual void OnCommand( const char *pCommand )
+ {
+ if ( FStrEq( pCommand, "goprev" ) )
+ {
+ GoPrev();
+ }
+ else if ( FStrEq( pCommand, "gonext" ) )
+ {
+ GoNext();
+ }
+ else
+ {
+ BaseClass::OnCommand( pCommand );
+ }
+ }
+
+ virtual void OnKeyCodePressed( KeyCode nCode )
+ {
+ ButtonCode_t nButtonCode = GetBaseButtonCode( nCode );
+
+ if ( nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == KEY_LEFT )
+ {
+ GoPrev();
+ }
+ else if ( nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == KEY_RIGHT )
+ {
+ GoNext();
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( nCode );
+ }
+ }
+
+ void GoPrev()
+ {
+ --m_iPage;
+
+ if ( m_iPage < 0 )
+ {
+ m_iPage += GetNumPages();
+ }
+
+ InvalidateLayout( false, true );
+ }
+
+ void GoNext()
+ {
+ m_iPage = ( m_iPage + 1 ) % GetNumPages();
+
+ InvalidateLayout( false, true );
+ }
+
+ virtual int GetNumPages() const = 0;
+
+protected:
+ int m_iPage;
+};
+
+//------------------------------------------------------------------------------------------------------
+
+class CModePanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CModePanel, EditablePanel );
+public:
+ CModePanel( Panel *pParent, const char *pName )
+ : EditablePanel( pParent, pName )
+ {
+ HScheme hScheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" );
+ SetScheme( hScheme );
+ SetProportional( true );
+ }
+
+ ~CModePanel()
+ {
+ }
+
+ virtual void ApplySettings( KeyValues *pInResourceData )
+ {
+ BaseClass::ApplySettings( pInResourceData );
+
+ m_strModeNameToken = pInResourceData->GetString( "modename", NULL );
+ m_strDescriptionToken = pInResourceData->GetString( "description", NULL );
+ m_strImageToken = pInResourceData->GetString( "image", NULL );
+ m_strStartCommand = pInResourceData->GetString( "startcommand", NULL );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/training/modeselection/modepanel.res" );
+
+ EditablePanel *pContainer = static_cast< EditablePanel * >( FindChildByName( "ModeInfoContainer" ) );
+ if ( pContainer )
+ {
+ pContainer->SetDialogVariable( "modename", g_pVGuiLocalize->Find( m_strModeNameToken.Get() ) );
+ pContainer->SetDialogVariable( "description", g_pVGuiLocalize->Find( m_strDescriptionToken.Get() ) );
+
+ EditablePanel *pImageFrame = static_cast< EditablePanel * >( pContainer->FindChildByName( "ImageFrame" ) );
+ if ( pImageFrame )
+ {
+ ImagePanel *pImage = dynamic_cast< ImagePanel * >( pContainer->FindChildByName( "Image" ) );
+ if ( pImage )
+ {
+ pImage->SetImage( m_strImageToken );
+ pImage->SetParent( pImageFrame );
+ }
+ }
+ }
+
+ SetupButtonActionSignalTarget( this, "StartButton", m_strStartCommand.Get() );
+ }
+
+ virtual void PerformLayout( void )
+ {
+ BaseClass::PerformLayout();
+
+ GetParent()->NavigateTo();
+ }
+
+private:
+ CUtlString m_strModeNameToken;
+ CUtlString m_strDescriptionToken;
+ CUtlString m_strImageToken;
+ CUtlString m_strStartCommand;
+};
+
+DECLARE_BUILD_FACTORY( CModePanel );
+
+//------------------------------------------------------------------------------------------------------
+
+class CModeSelectionPanel : public CTrainingBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CModeSelectionPanel, CTrainingBasePanel );
+public:
+ CModeSelectionPanel( Panel *pParent, const char *pName )
+ : CTrainingBasePanel( pParent, pName )
+ {
+ SetProportional( true );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/training/modeselection/modeselection.res" );
+ }
+
+ virtual bool IsFirstPage() const
+ {
+ return true;
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ Panel *pPanel = FindChildByName( "BasicTrainingPanel" );
+ if ( pPanel )
+ {
+ pPanel->SetNavToRelay( "StartButton" );
+ pPanel->SetNavRight( "<<OfflinePracticePanel" );
+ pPanel->InvalidateLayout();
+ }
+
+ pPanel = FindChildByName( "OfflinePracticePanel" );
+ if ( pPanel )
+ {
+ pPanel->SetNavToRelay( "StartButton" );
+ pPanel->SetNavLeft( "<<BasicTrainingPanel" );
+ pPanel->InvalidateLayout();
+ }
+
+ SetNavToRelay( "BasicTrainingPanel" );
+ }
+};
+
+DECLARE_BUILD_FACTORY( CModeSelectionPanel );
+
+//------------------------------------------------------------------------------------------------------
+
+class CBasicTraining_ClassPanel : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBasicTraining_ClassPanel, EditablePanel );
+public:
+ CBasicTraining_ClassPanel( Panel *pParent, const char *pName )
+ : EditablePanel( pParent, pName ),
+ m_pImagePanel( NULL ),
+ m_pSelectButton( NULL )
+ {
+ SetProportional( true );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/training/basictraining/classpanel.res" );
+
+ m_pImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "Image" ) ); Assert( m_pImagePanel );
+ m_pSelectButton = SetupButtonActionSignalTarget( this, "SelectButton" ); Assert( m_pSelectButton );
+
+ if ( m_pSelectButton )
+ {
+ m_pSelectButton->SetDefaultBorder( pScheme->GetBorder( m_pSelectButton->IsEnabled() ? "MainMenuButtonDefault" : "MainMenuButtonDisabled" ) );
+ }
+
+ m_pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) );
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ if ( m_pImagePanel && m_pSelectButton )
+ {
+ const int nMargin = XRES( 10 );
+ const int nButtonStartY = YRES( 215 );
+
+ m_pImagePanel->SetBounds( nMargin, YRES( 20 ), GetWide() - 2 * nMargin, nButtonStartY - YRES( 40 ) );
+
+ int aButtonBounds[4] = {
+ nMargin, nButtonStartY, GetWide() - nMargin * 2, (int)YRES( 25 )
+ };
+
+ m_pSelectButton->SetBounds( aButtonBounds[0], aButtonBounds[1], aButtonBounds[2], aButtonBounds[3] );
+
+ CExLabel *pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) );
+ if ( pProgressLabel )
+ {
+ int aPos[2];
+ pProgressLabel->GetPos( aPos[0], aPos[1] );
+ pProgressLabel->SetPos( aButtonBounds[0], aPos[1] );
+ pProgressLabel->SetWide( aButtonBounds[2] );
+ }
+ }
+
+ GetParent()->NavigateTo();
+ }
+
+ void SetClassData( int iClass, int nProgress, const char *pImageBase )
+ {
+ const bool bLocked = nProgress < 0;
+
+ if ( m_pImagePanel )
+ {
+ CFmtStr fmtImagePath( "%s_%s", pImageBase, bLocked ? "off" : "on" );
+ m_pImagePanel->SetImage( fmtImagePath.Access() );
+ }
+
+ if ( m_pSelectButton )
+ {
+ m_pSelectButton->SetEnabled( !bLocked );
+ }
+
+ if ( m_pProgressLabel )
+ {
+ const int nPercent = (int)( 100.0f * nProgress / Training_GetNumCoursesForClass( iClass ) );
+ wchar_t wszLocalized[256];
+ if ( nPercent > 0 )
+ {
+ if ( nPercent < 100 )
+ {
+ wchar_t wszNum[16] = L"";
+ V_snwprintf( wszNum, ARRAYSIZE( wszNum ), L"%i", nPercent );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TR_Progress" ), 1, wszNum );
+ }
+ else
+ {
+ V_wcsncpy( wszLocalized, g_pVGuiLocalize->Find( "#TR_ProgressDone" ), sizeof( wszLocalized ) );
+ }
+
+ m_pProgressLabel->SetText( wszLocalized );
+ m_pProgressLabel->SetVisible( true );
+ }
+ else
+ {
+ m_pProgressLabel->SetVisible( false );
+ }
+ }
+
+ InvalidateLayout( true, false );
+ }
+
+ void SetSelectCommand( const char *pCommand )
+ {
+ if ( m_pSelectButton )
+ {
+ m_pSelectButton->SetCommand( pCommand );
+ }
+ }
+
+private:
+ ImagePanel *m_pImagePanel;
+ CExButton *m_pSelectButton;
+ CExLabel *m_pProgressLabel;
+};
+
+DECLARE_BUILD_FACTORY( CBasicTraining_ClassPanel );
+
+//------------------------------------------------------------------------------------------------------
+
+enum Consts_t
+{
+ NUM_CLASS_PANELS = 4,
+};
+
+const char *g_pClassPanelNames[ NUM_CLASS_PANELS ] =
+{
+ "SoldierPanel",
+ "DemoPanel",
+ "SpyPanel",
+ "EngineerPanel"
+};
+
+class CBasicTraining_ClassSelectionPanel : public CTrainingBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CBasicTraining_ClassSelectionPanel, CTrainingBasePanel );
+public:
+ CBasicTraining_ClassSelectionPanel( Panel *pParent, const char *pName )
+ : CTrainingBasePanel( pParent, pName )
+ {
+ SetProportional( true );
+
+ for ( int i = 0; i < NUM_CLASS_PANELS; ++i )
+ {
+ m_PanelInfos[ i ].m_pPanel = new CBasicTraining_ClassPanel( this, g_pClassPanelNames[ i ] );
+ }
+ }
+
+ virtual void ApplySettings( KeyValues *pInResourceData )
+ {
+ BaseClass::ApplySettings( pInResourceData );
+
+ for ( int i = 0; i < NUM_CLASS_PANELS; ++i )
+ {
+ CFmtStr fmtToken( "Class%iToken", i );
+ m_PanelInfos[ i ].m_strSelectButtonToken = pInResourceData->GetString( fmtToken.Access(), NULL );
+
+ CFmtStr fmtImage( "Class%iImage", i );
+ m_PanelInfos[ i ].m_strClassImage = pInResourceData->GetString( fmtImage.Access(), NULL );
+
+ CFmtStr fmtCommand( "Class%iCommand", i );
+ m_PanelInfos[ i ].m_strCommand = pInResourceData->GetString( fmtCommand.Access(), NULL );
+ }
+
+ PRINT_KEY_VALUES( pInResourceData );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/training/basictraining/classselection.res" );
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ const int nWidth = GetWide();
+ const int nClassPanelW = nWidth / NUM_CLASS_PANELS;
+ const int nClassPanelH = YRES( 260 );
+
+ const int aTrainingClasses[ NUM_CLASS_PANELS ] = {
+ TF_CLASS_SOLDIER, TF_CLASS_DEMOMAN, TF_CLASS_SPY, TF_CLASS_ENGINEER
+ };
+
+
+ for ( int i = 0; i < NUM_CLASS_PANELS; ++i )
+ {
+ CBasicTraining_ClassPanel *pCurClassPanel = m_PanelInfos[ i ].m_pPanel;
+
+ pCurClassPanel->SetBounds(
+ i * nClassPanelW,
+ 0,
+ nClassPanelW,
+ nClassPanelH
+ );
+
+ pCurClassPanel->SetDialogVariable( "selectbuttontext", g_pVGuiLocalize->Find( m_PanelInfos[ i ].m_strSelectButtonToken.Get() ) );
+
+ const int nProgress = Training_GetClassProgress( aTrainingClasses[ i ] );
+ pCurClassPanel->SetClassData( aTrainingClasses[ i ], nProgress, m_PanelInfos[ i ].m_strClassImage.Get() );
+
+ pCurClassPanel->SetSelectCommand( m_PanelInfos[ i ].m_strCommand.Get() );
+
+ pCurClassPanel->SetNavToRelay( "SelectButton" );
+
+ char szName[ 64 ];
+ if ( i > 0 )
+ {
+ Panel *pPrevPanel = m_PanelInfos[ i - 1 ].m_pPanel;
+ if ( pPrevPanel )
+ {
+ V_snprintf( szName, sizeof( szName ), "<%s", pPrevPanel->GetName() );
+ pCurClassPanel->SetNavLeft( szName );
+
+ V_snprintf( szName, sizeof( szName ), "<%s", pCurClassPanel->GetName() );
+ pPrevPanel->SetNavRight( szName );
+ }
+ }
+
+ pCurClassPanel->InvalidateLayout();
+ }
+
+ SetNavToRelay( g_pClassPanelNames[ 0 ] );
+ }
+
+ virtual bool ShouldShowGradient() const
+ {
+ return true;
+ }
+
+private:
+ struct ClassPanelInfo_t
+ {
+ CBasicTraining_ClassPanel *m_pPanel;
+ CUtlString m_strSelectButtonToken;
+ CUtlString m_strClassImage;
+ CUtlString m_strCommand;
+ }
+ m_PanelInfos[ NUM_CLASS_PANELS ];
+};
+
+DECLARE_BUILD_FACTORY( CBasicTraining_ClassSelectionPanel );
+
+//------------------------------------------------------------------------------------------------------
+
+class CBasicTraining_ClassDetailsPanel : public CTrainingBasePanel
+{
+ DECLARE_CLASS_SIMPLE( CBasicTraining_ClassDetailsPanel, CTrainingBasePanel );
+public:
+ CBasicTraining_ClassDetailsPanel( Panel *pParent, const char *pName )
+ : CTrainingBasePanel( pParent, pName ),
+ m_iClass( TF_CLASS_UNDEFINED ),
+ m_pStartTrainingButton( NULL )
+ {
+ SetProportional( true );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/training/basictraining/classdetails.res" );
+
+ EditablePanel *pOverlayPanel = dynamic_cast< EditablePanel * >( FindChildByName( "OverlayPanel" ) );
+ if ( pOverlayPanel && m_iClass >= TF_FIRST_NORMAL_CLASS && m_iClass < TF_LAST_NORMAL_CLASS )
+ {
+ pOverlayPanel->SetDialogVariable( "classname", g_pVGuiLocalize->Find( g_aPlayerClassNames[ m_iClass ] ) );
+
+ CFmtStr fmtDescToken( "TR_ClassInfo_%s", m_szClassName );
+ pOverlayPanel->SetDialogVariable( "description", g_pVGuiLocalize->Find( fmtDescToken.Access() ) );
+
+ for ( int i = 0; i < 3; ++i )
+ {
+ CFmtStr fmtWeaponImageName( "WeaponImage%i", i );
+ ImagePanel *pCurImage = dynamic_cast< ImagePanel * >( pOverlayPanel->FindChildByName( fmtWeaponImageName.Access() ) );
+
+ if ( pCurImage )
+ {
+ CFmtStr fmtWeaponImagePath;
+ GetWeaponPath( m_iClass, i, fmtWeaponImagePath );
+ pCurImage->SetImage( fmtWeaponImagePath.Access() );
+ }
+ }
+ }
+
+ ImagePanel *pClassImage = dynamic_cast< ImagePanel * >( FindChildByName( "ClassImage" ) );
+ if ( pClassImage )
+ {
+ CFmtStr fmtImageName( "training/class_%s_on", m_szClassName );
+ pClassImage->SetImage( fmtImageName.Access() );
+ }
+
+ ImagePanel *pClassIconImage = dynamic_cast< ImagePanel * >( FindChildByName( "ClassIconImage" ) );
+ if ( pClassIconImage )
+ {
+ CFmtStr fmtImageName( "training/class_icon_%s", m_szClassName );
+ pClassIconImage->SetImage( fmtImageName.Access() );
+ }
+
+ m_pStartTrainingButton = SetupButtonActionSignalTarget( this, "StartTrainingButton" );
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ SetNavToRelay( "StartTrainingButton" );
+ NavigateTo();
+ }
+
+ void GetWeaponPath( int iClass, int iWeapon, CFmtStr &fmtOut ) // iWeapon is in [0,2]
+ {
+ static const char *s_pWeaponNames[ TF_CLASS_COUNT ][ 3 ] = {
+ { NULL, NULL, NULL }, // TF_CLASS_UNDEFINED
+ { NULL, NULL, NULL }, // TF_CLASS_SCOUT
+ { NULL, NULL, NULL }, // TF_CLASS_SNIPER
+ { "rocketlauncher", "shotgun", "shovel" }, // TF_CLASS_SOLDIER
+ { "grenadelauncher", "stickybomb_launcher", "bottle" }, // TF_CLASS_DEMOMAN,
+ { NULL, NULL, NULL }, // TF_CLASS_MEDIC
+ { NULL, NULL, NULL }, // TF_CLASS_HEAVYWEAPONS
+ { NULL, NULL, NULL }, // TF_CLASS_PYRO
+ { "revolver", "c_spy_watch", "knife", }, // TF_CLASS_SPY,
+ { "shotgun", "pistol", "wrench" }, // TF_CLASS_ENGINEER,
+ };
+
+ Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_CLASS_COUNT );
+ Assert( iWeapon >= 0 && iWeapon < 3 );
+
+ if ( iClass == TF_CLASS_SPY && iWeapon == 1 )
+ {
+ fmtOut.sprintf( "../backpack/weapons/c_models/c_spy_watch/parts/c_spy_watch" );
+ }
+ else
+ {
+ fmtOut.sprintf( "../backpack/weapons/w_models/w_%s", s_pWeaponNames[ iClass ][ iWeapon ] );
+ }
+ }
+
+ virtual bool ShouldShowGradient() const
+ {
+ return true;
+ }
+
+ virtual const char *GetGoCommand() const
+ {
+ if ( !m_pStartTrainingButton )
+ return NULL;
+
+ KeyValues *pCommand = m_pStartTrainingButton->GetCommand();
+ if ( !pCommand )
+ return NULL;
+
+ return pCommand->GetString( "command", NULL );
+ }
+
+ void SetClass( const char *pClassName )
+ {
+ V_strcpy_safe( m_szClassName, pClassName );
+
+ // Setup class details panel
+ if ( FStrEq( pClassName, "soldier" ) )
+ {
+ m_iClass = TF_CLASS_SOLDIER;
+ }
+ else if ( FStrEq( pClassName, "demoman" ) )
+ {
+ m_iClass = TF_CLASS_DEMOMAN;
+ }
+ else if ( FStrEq( pClassName, "spy" ) )
+ {
+ m_iClass = TF_CLASS_SPY;
+ }
+ else if ( FStrEq( pClassName, "engineer" ) )
+ {
+ m_iClass = TF_CLASS_ENGINEER;
+ }
+ else
+ {
+ AssertMsg( 0, "Bad class name." );
+ }
+ }
+
+private:
+ char m_szClassName[16];
+ int m_iClass;
+ CExButton *m_pStartTrainingButton;
+};
+
+DECLARE_BUILD_FACTORY( CBasicTraining_ClassDetailsPanel );
+
+//------------------------------------------------------------------------------------------------------
+
+class COfflinePractice_ModeSelectionPanel : public CTrainingBaseCarouselPanel
+{
+ DECLARE_CLASS_SIMPLE( COfflinePractice_ModeSelectionPanel, CTrainingBaseCarouselPanel );
+public:
+ COfflinePractice_ModeSelectionPanel( Panel *pParent, const char *pName )
+ : CTrainingBaseCarouselPanel( pParent, pName ),
+ m_pGameModeImagePanel( NULL )
+ {
+ SetProportional( true );
+ }
+
+ virtual void ApplySettings( KeyValues *pInResourceData )
+ {
+ BaseClass::ApplySettings( pInResourceData );
+
+ for ( int i = 0; i < NUM_PRACTICE_MODES; ++i )
+ {
+ CFmtStr fmtModeToken( "Mode%iToken", i );
+ m_ModeInfos[ i ].m_strModeToken = pInResourceData->GetString( fmtModeToken.Access(), NULL );
+
+ CFmtStr fmtDescToken( "Desc%iToken", i );
+ m_ModeInfos[ i ].m_strDescToken = pInResourceData->GetString( fmtDescToken.Access(), NULL );
+
+ CFmtStr fmtImagePath( "Image%iPath", i );
+ m_ModeInfos[ i ].m_strImage = pInResourceData->GetString( fmtImagePath.Access(), NULL );
+
+ CFmtStr fmtModeId( "Mode%iId", i );
+ m_ModeInfos[ i ].m_nId = ( GameMode_t )pInResourceData->GetInt( fmtModeId.Access(), MODE_INVALID ); Assert( m_ModeInfos[ i ].m_nId != MODE_INVALID );
+ }
+
+ PRINT_KEY_VALUES( pInResourceData );
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/training/offlinepractice/practicemodeselection.res" );
+
+ m_pGameModeImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "GameModeImagePanel" ) );
+ if ( m_pGameModeImagePanel )
+ {
+ Assert( m_iPage >= 0 && m_iPage < NUM_PRACTICE_MODES );
+ m_pGameModeImagePanel->SetImage( m_ModeInfos[ m_iPage ].m_strImage.Get() );
+ }
+
+ SetupButtonActionSignalTarget( this, "SelectCurrentGameModeButton" );
+
+ SetDialogVariable( "description", g_pVGuiLocalize->Find( m_ModeInfos[ m_iPage ].m_strDescToken.Get() ) );
+ SetDialogVariable( "gamemode", g_pVGuiLocalize->Find( m_ModeInfos[ m_iPage ].m_strModeToken.Get() ) );
+
+ CFmtStr fmtCurPageLabelText( "%i/%i", m_iPage + 1, NUM_PRACTICE_MODES );
+ SetDialogVariable( "curpage", fmtCurPageLabelText.Access() );
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ if ( m_pGameModeImagePanel )
+ {
+ // Use .res file ypos
+ int aPos[2];
+ m_pGameModeImagePanel->GetPos( aPos[0], aPos[1] );
+
+ // Center
+ m_pGameModeImagePanel->SetPos( ( GetWide() - m_pGameModeImagePanel->GetWide() ) / 2, aPos[1] );
+ }
+
+ SetNavToRelay( "SelectCurrentGameModeButton" );
+ NavigateTo();
+ }
+
+ virtual int GetNumPages() const
+ {
+ return NUM_PRACTICE_MODES;
+ }
+
+ GameMode_t GetMode() const
+ {
+ return m_ModeInfos[ m_iPage ].m_nId;
+ }
+
+private:
+ enum Consts_t
+ {
+ NUM_PRACTICE_MODES = 3,
+ };
+
+ struct PracticeModeInfo_t
+ {
+ CUtlString m_strModeToken;
+ CUtlString m_strDescToken;
+ CUtlString m_strImage;
+ GameMode_t m_nId;
+ }
+ m_ModeInfos[ NUM_PRACTICE_MODES ];
+
+ ImagePanel *m_pGameModeImagePanel;
+};
+
+DECLARE_BUILD_FACTORY( COfflinePractice_ModeSelectionPanel );
+
+
+const char *g_pDifficultyModes[ 4 ] = { "Easy", "Normal", "Hard", "Expert" };
+
+//------------------------------------------------------------------------------------------------------
+
+class COfflinePractice_MapSelectionPanel : public CTrainingBaseCarouselPanel
+{
+ DECLARE_CLASS_SIMPLE( COfflinePractice_MapSelectionPanel, CTrainingBaseCarouselPanel );
+
+ struct MapInfo_t
+ {
+ CUtlString m_strDisplayName;
+ CUtlString m_strName;
+ int m_aPlayerRange[2];
+ };
+
+public:
+ COfflinePractice_MapSelectionPanel( Panel *pParent, const char *pName )
+ : CTrainingBaseCarouselPanel( pParent, pName ),
+ m_pMapImagePanel( NULL ),
+ m_pDefaultsData( NULL ),
+ m_pDifficultyComboBox( NULL ),
+ m_pSavedData( NULL ),
+ m_iGameMode( MODE_INVALID )
+ {
+ SetProportional( true );
+ LoadMapData();
+ }
+
+ ~COfflinePractice_MapSelectionPanel()
+ {
+ for ( int i = 0; i < NUM_GAME_MODES; ++i )
+ {
+ m_vecMapData[i].PurgeAndDeleteElements();
+ }
+
+ if ( m_pDefaultsData )
+ {
+ m_pDefaultsData->deleteThis();
+ }
+ }
+
+ void SetGameMode( int iGameMode )
+ {
+ m_iGameMode = iGameMode;
+ m_iPage = 0;
+ InvalidateLayout( false, true );
+ }
+
+ const MapInfo_t *GetSelectedMapInfo() const
+ {
+ return m_iGameMode < 0 ? NULL : m_vecMapData[ m_iGameMode ][ m_iPage ];
+ }
+
+ int GetMaxPlayers() const
+ {
+ return GetSelectedMapInfo()->m_aPlayerRange[1];
+ }
+
+ const char *GetMapName() const
+ {
+ return GetSelectedMapInfo()->m_strName.Get();
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ LoadControlSettings( "resource/ui/training/offlinepractice/mapselection.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ const MapInfo_t *pCurMapInfo = GetSelectedMapInfo();
+ if ( !pCurMapInfo )
+ return;
+
+ m_pMapImagePanel = dynamic_cast< ImagePanel * >( FindChildByName( "MapImagePanel" ) );
+ if ( m_pMapImagePanel )
+ {
+ Assert( m_iPage >= 0 && m_iPage < GetMapCount() );
+
+ CFmtStr fmtMapImageBasePath( "training/screenshots/%s.vmt", pCurMapInfo->m_strName.Get() );
+ m_pMapImagePanel->SetImage( fmtMapImageBasePath.Access() );
+ }
+
+ // Send the 'select' button's command to the actual dialog
+ SetupButtonActionSignalTarget( this, "SelectCurrentMapButton" );
+
+ // update recommended number of players
+ CExLabel *pSuggestedPlayerCountLabel = dynamic_cast< CExLabel * >( FindChildByName( "SuggestedPlayerCountLabel" ) );
+ if ( pSuggestedPlayerCountLabel )
+ {
+ wchar_t wszLocalized[256];
+ wchar_t wszNum1[16]=L"";
+ wchar_t wszNum2[16]=L"";
+ V_snwprintf( wszNum1, ARRAYSIZE( wszNum1 ), L"%i", pCurMapInfo->m_aPlayerRange[0] );
+ V_snwprintf( wszNum2, ARRAYSIZE( wszNum2 ), L"%i", pCurMapInfo->m_aPlayerRange[1] );
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_OfflinePractice_NumPlayers" ), 2, wszNum1, wszNum2 );
+ pSuggestedPlayerCountLabel->SetText( wszLocalized );
+ }
+
+ m_pDifficultyComboBox = dynamic_cast< ComboBox * >( FindChildByName( "DifficultyComboBox" ) );
+ if ( m_pDifficultyComboBox )
+ {
+ for ( int i = 0; i < ARRAYSIZE( g_pDifficultyModes ); ++i )
+ {
+ m_pDifficultyComboBox->AddItem( g_pDifficultyModes[i], NULL );
+ }
+ }
+
+ TextEntry *pNumPlayersTextEntry = dynamic_cast< TextEntry * >( FindChildByName( "NumPlayersTextEntry" ) );
+ if ( pNumPlayersTextEntry )
+ {
+ pNumPlayersTextEntry->SetBorder( pScheme->GetBorder( "ComboBoxBorder" ) );
+ }
+
+ SetupButtonActionSignalTarget( this, "StartOfflinePracticeButton" );
+
+ SetDialogVariable( "mapname", pCurMapInfo->m_strDisplayName.Get() );
+
+ UpdateControlsFromSavedData( m_pDifficultyComboBox, pNumPlayersTextEntry );
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ if ( m_pMapImagePanel )
+ {
+ // Use .res file ypos
+ int aPos[2];
+ m_pMapImagePanel->GetPos( aPos[0], aPos[1] );
+
+ // Center
+ m_pMapImagePanel->SetPos( ( GetWide() - m_pMapImagePanel->GetWide() ) / 2, aPos[1] );
+ }
+
+ SetNavToRelay( "StartOfflinePracticeButton" );
+ NavigateTo();
+ }
+
+ virtual void OnKeyCodePressed( KeyCode nCode )
+ {
+ ButtonCode_t nButtonCode = GetBaseButtonCode( nCode );
+
+ if ( nButtonCode == KEY_XBUTTON_X )
+ {
+ if ( m_pDifficultyComboBox )
+ {
+ m_pDifficultyComboBox->SilentActivateItemByRow( ( m_pDifficultyComboBox->GetActiveItem() + 1 ) % ARRAYSIZE( g_pDifficultyModes ) );
+ }
+ }
+ else if ( nButtonCode == KEY_XBUTTON_UP ||
+ nButtonCode == KEY_XSTICK1_UP ||
+ nButtonCode == KEY_XSTICK2_UP ||
+ nButtonCode == KEY_UP )
+ {
+ SetControlInt( "NumPlayersTextEntry", clamp( GetControlInt( "NumPlayersTextEntry", 0 ) + 1, 1, 31 ) );
+ }
+ else if ( nButtonCode == KEY_XBUTTON_DOWN ||
+ nButtonCode == KEY_XSTICK1_DOWN ||
+ nButtonCode == KEY_XSTICK2_DOWN ||
+ nButtonCode == KEY_RIGHT )
+ {
+ SetControlInt( "NumPlayersTextEntry", clamp( GetControlInt( "NumPlayersTextEntry", 0 ) - 1, 1, 31 ) );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( nCode );
+ }
+ }
+
+ virtual int GetNumPages() const
+ {
+ return GetMapCount();
+ }
+
+ int GetMapCount() const
+ {
+ return m_vecMapData[ m_iGameMode ].Count();
+ }
+
+ void GetControlValues( int *pOutNumPlayers, int *pOutDiff, CUtlString *pOutMap = NULL )
+ {
+ const MapInfo_t *pSelectedMapInfo = GetSelectedMapInfo();
+ if ( !pSelectedMapInfo )
+ return;
+
+ *pOutNumPlayers = clamp( GetControlInt( "NumPlayersTextEntry", 0 ), 1, 31 );
+
+ *pOutDiff = clamp( GetBotDifficulty(), 0, 3 );
+
+ if ( pOutMap )
+ {
+ *pOutMap = pSelectedMapInfo->m_strName;
+ }
+ }
+
+ bool DoSetup()
+ {
+ // @note Tom Bui: if you add any other convars that get set, please revert them
+ // in CTFBotManager::RevertOfflinePracticeConvars()
+
+ const MapInfo_t *pSelectedMapInfo = GetSelectedMapInfo();
+ if ( !pSelectedMapInfo )
+ return false;
+
+ int nQuota = 1;
+ int iDifficulty = 0;
+ GetControlValues( &nQuota, &iDifficulty );
+
+ // the player count in the dialog includes the human player, so decrease the bot count by one
+ ConVarRef tf_bot_quota( "tf_bot_quota" );
+ tf_bot_quota.SetValue( nQuota - 1 );
+
+ ConVarRef tf_bot_quota_mode( "tf_bot_quota_mode" );
+ tf_bot_quota_mode.SetValue( "normal" );
+
+ ConVarRef tf_bot_auto_vacate( "tf_bot_auto_vacate" );
+ tf_bot_auto_vacate.SetValue( 0 );
+
+ ConVarRef tf_bot_difficulty( "tf_bot_difficulty" );
+ tf_bot_difficulty.SetValue( iDifficulty );
+
+ ConVarRef tf_bot_offline_practice( "tf_bot_offline_practice" );
+ tf_bot_offline_practice.SetValue( 1 );
+
+ tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_WATCHING_INTRO_MOVIE );
+
+ SaveSettings();
+
+ return true;
+ }
+
+private:
+ virtual KeyValues *GetTitleFormatData() const
+ {
+ KeyValues *pResult = new KeyValues( "data" );
+ if ( pResult )
+ {
+ const char *pGameModeToken = ( m_iGameMode >= 0 && m_iGameMode < NUM_GAME_MODES ) ? gs_pGameModeTokens[ m_iGameMode ] : "";
+ pResult->SetWString( "gametype", g_pVGuiLocalize->Find( pGameModeToken ) );
+ }
+ return pResult;
+ }
+
+ virtual void OnBackPressed()
+ {
+ SaveSettings();
+ }
+
+ void SaveSettings()
+ {
+ // Save settings
+ if ( m_pSavedData )
+ {
+ int nNumPlayers = 1;
+ int iDifficulty = 0;
+ CUtlString strMap;
+ GetControlValues( &nNumPlayers, &iDifficulty, &strMap );
+
+ m_pSavedData->SetInt( "tf_bot_quota", nNumPlayers );
+ m_pSavedData->SetInt( "tf_bot_difficulty", iDifficulty );
+ m_pSavedData->SetString( "map", strMap.Get() );
+ }
+
+ if ( !m_pSavedData->SaveToFile( g_pFullFileSystem, "OfflinePracticeConfig.vdf", "MOD" ) )
+ {
+ Warning( "Failed to write save data to OfflinePracticeConfig.vdf!\n" );
+ }
+ }
+
+ int GetBotDifficulty() const
+ {
+ if ( m_pDifficultyComboBox )
+ {
+ return m_pDifficultyComboBox->GetActiveItem();
+ }
+
+ AssertMsg( 0, "Shouldn't get here." );
+ return 0;
+ }
+
+ void UpdateControlsFromSavedData( ComboBox *pDifficultyComboBox, TextEntry *pNumPlayersTextEntry )
+ {
+ if ( !pDifficultyComboBox )
+ return;
+
+ if ( !pNumPlayersTextEntry )
+ return;
+
+ int iDifficulty = -1;
+ int nQuota = 0;
+ const char *defaultMap = "";
+
+ if ( m_pSavedData )
+ {
+ m_pSavedData->deleteThis();
+ m_pSavedData = NULL;
+ }
+
+ m_pSavedData = new KeyValues( "OfflinePracticeConfig" );
+
+ // load the config data
+ if ( m_pSavedData )
+ {
+ // this is game-specific data, so it should live in GAME, not CONFIG
+ if ( m_pSavedData->LoadFromFile( g_pFullFileSystem, "OfflinePracticeConfig.vdf", "MOD" ) )
+ {
+ iDifficulty = m_pSavedData->GetInt( "tf_bot_difficulty", -1 );
+ nQuota = m_pSavedData->GetInt( "tf_bot_quota", 0 );
+ defaultMap = m_pSavedData->GetString( "map", "" );
+ }
+ }
+
+ if ( m_pDefaultsData )
+ {
+ const int nMaxPlayers = m_pDefaultsData->GetInt( "max_players" );
+
+ if ( FStrEq( defaultMap, "" ) )
+ {
+ defaultMap = m_pDefaultsData->GetString( "map", "" );
+ }
+
+ if ( nQuota == 0 )
+ {
+ nQuota = m_pDefaultsData->GetInt( "suggested_players", nMaxPlayers );
+ }
+
+ if ( iDifficulty == -1 )
+ {
+ const char *pDifficultyString = m_pDefaultsData->GetString( "difficulty" );
+ if ( pDifficultyString )
+ {
+ static const char* difficulties [] = { "easy", "normal", "hard", "expert" };
+ for ( int i = 0, n = ARRAYSIZE(difficulties); i < n; ++i )
+ {
+ if ( Q_strcmp( difficulties[i], pDifficultyString ) == 0)
+ {
+ iDifficulty = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Set values in controls
+ m_pDifficultyComboBox->SilentActivateItemByRow( iDifficulty );
+
+ CFmtStr fmtNumPlayers( "%i", nQuota );
+ pNumPlayersTextEntry->SetText( fmtNumPlayers.Access() );
+ }
+
+ void LoadMapData()
+ {
+ m_pOfflinePracticeData = new KeyValues( "offline_practice.res" );
+ const char *pFilename = "resource/offline_practice.res";
+ if ( !m_pOfflinePracticeData->LoadFromFile( g_pFullFileSystem, pFilename, "MOD" ) )
+ {
+ Warning( "Could not load %s!\n", pFilename );
+ return;
+ }
+
+ // Save defaults
+ KeyValues *pDefaultsData = m_pOfflinePracticeData->FindKey( "defaults" );
+ if ( pDefaultsData )
+ {
+ m_pDefaultsData = pDefaultsData->MakeCopy();
+ }
+
+ KeyValues *pMapData = m_pOfflinePracticeData->FindKey( "maps" );
+ if ( pMapData )
+ {
+ FOR_EACH_TRUE_SUBKEY( pMapData, pCurMap )
+ {
+ MapInfo_t *pMapInfo = new MapInfo_t;
+
+ pMapInfo->m_strName = pCurMap->GetName();
+ pMapInfo->m_strDisplayName = pCurMap->GetString( "name" );
+ pMapInfo->m_aPlayerRange[0] = pCurMap->GetInt( "min_players" );
+ pMapInfo->m_aPlayerRange[1] = pCurMap->GetInt( "max_players" );
+
+ // Figure out which bucket to add to
+ const GameMode_t iGameMode = GetGameModeFromMapName( pMapInfo->m_strName.Get() );
+ if ( iGameMode != MODE_INVALID )
+ {
+ AddMapInfo( pMapInfo, iGameMode );
+ }
+ }
+ }
+
+ pMapData->deleteThis();
+ }
+
+ GameMode_t GetGameModeFromMapName( const char *pMapName )
+ {
+ if ( !V_strnicmp( pMapName, "cp", 2 ) )
+ {
+ return MODE_CP;
+ }
+ else if ( !V_strnicmp( pMapName, "koth", 4 ) )
+ {
+ return MODE_KOTH;
+ }
+ else if ( !V_strnicmp( pMapName, "pl", 2 ) )
+ {
+ return MODE_PL;
+ }
+
+ AssertMsg( 0, "Should never get here!" );
+
+ return MODE_INVALID;
+ }
+
+ void AddMapInfo( MapInfo_t *pMapInfo, GameMode_t iGameMode )
+ {
+ m_vecMapData[ iGameMode ].AddToTail( pMapInfo );
+ }
+
+ int m_iGameMode;
+ KeyValues *m_pSavedData;
+ KeyValues *m_pDefaultsData;
+ ImagePanel *m_pMapImagePanel;
+ ComboBox *m_pDifficultyComboBox;
+ KeyValues *m_pOfflinePracticeData;
+ CUtlVector< MapInfo_t * > m_vecMapData[ NUM_GAME_MODES ];
+};
+
+DECLARE_BUILD_FACTORY( COfflinePractice_MapSelectionPanel );
+
+//------------------------------------------------------------------------------------------------------
+
+class CTrainingDialog : public EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTrainingDialog, EditablePanel );
+public:
+ CTrainingDialog( Panel *parent )
+ : EditablePanel( parent, TRAINING_DIALOG_NAME ),
+ m_pBackButton( NULL ),
+ m_pCancelButton( NULL ),
+ m_pGradientBgPanel( NULL ),
+ m_pModeSelectionPanel( NULL ),
+ m_pCurrentPagePanel( NULL ),
+ m_pBasicTraining_ClassSelectionPanel( NULL ),
+ m_pBasicTraining_ClassDetailsPanel( NULL ),
+ m_pOfflinePractice_ModeSelectionPanel( NULL ),
+ m_pOfflinePractice_MapSelectionPanel( NULL ),
+ m_pTrainingData( NULL ),
+ m_bStartTraining( false ),
+ m_bContinue( false )
+ {
+ HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme" );
+ SetScheme(scheme);
+ SetProportional( true );
+ m_pContainer = new EditablePanel( this, "Container" );
+
+ // load configuration
+ const char *filename = "resource/training.res";
+ m_pTrainingData = new KeyValues( "training.res" );
+ Assert( m_pTrainingData );
+ if ( !m_pTrainingData->LoadFromFile( g_pFullFileSystem, filename, "MOD" ) )
+ {
+ Warning( "Unable to load '%s'\n", filename );
+ AssertMsg( 0, "Couldn't load training data!" );
+ }
+
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "training" );
+ }
+
+ virtual ~CTrainingDialog()
+ {
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_close", "training" );
+
+ ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+ virtual void SetDialogVariable( const char *pVarName, const char *pValue )
+ {
+ m_pContainer->SetDialogVariable( pVarName, pValue );
+ }
+
+ virtual void SetDialogVariable( const char *pVarName, const wchar_t *pValue )
+ {
+ m_pContainer->SetDialogVariable( pVarName, pValue );
+ }
+
+ virtual void SetDialogVariable( const char *pVarName, int nValue )
+ {
+ m_pContainer->SetDialogVariable( pVarName, nValue );
+ }
+
+ virtual void SetDialogVariable( const char *pVarName, float flValue )
+ {
+ m_pContainer->SetDialogVariable( pVarName, flValue );
+ }
+
+ void SetupButton( const char *pPanelName, CExButton **ppOut = NULL )
+ {
+ Panel *pPanel = m_pContainer->FindChildByName( pPanelName );
+ if ( pPanel )
+ {
+ pPanel->AddActionSignalTarget( this );
+ }
+
+ if ( ppOut )
+ {
+ *ppOut = static_cast< CExButton * >( pPanel );
+ }
+ }
+
+ virtual void Show()
+ {
+ SetVisible( true );
+ MakePopup();
+ MoveToFront();
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+ TFModalStack()->PushModal( this );
+ }
+
+ virtual void OnThink()
+ {
+ BaseClass::OnThink();
+ }
+
+ virtual void OnCommand( const char *pCommand )
+ {
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "on_command(training)", pCommand );
+
+ if ( FStrEq( pCommand, "prevpage" ) )
+ {
+ ShowPrevPage();
+ }
+ else if ( FStrEq( pCommand, "cancel" ) )
+ {
+ Close();
+ }
+ else if ( FStrEq( pCommand, "basictrainingselected" ) )
+ {
+ BasicTraining_ShowClassSelection();
+ }
+ else if ( FStrEq( pCommand, "offlinepracticeselected" ) )
+ {
+ OfflinePractice_ShowPracticeMode();
+ }
+ else if ( FStrEq( pCommand, "startbasictraining" ) )
+ {
+ BasicTraining_Start();
+ }
+ else if ( !V_strnicmp( pCommand, "basictraining_classselection_", 29 ) )
+ {
+ BasicTraining_ShowClassDetailsPage( pCommand + 29 );
+ }
+ else if ( FStrEq( pCommand, "selectcurrentgamemode" ) )
+ {
+ OfflinePractice_ShowMapSelection();
+ }
+ else if ( FStrEq( pCommand, "startofflinepractice" ) )
+ {
+ OfflinePractice_Start();
+ }
+ else
+ {
+ BaseClass::OnCommand( pCommand );
+ }
+ }
+
+ void SetCurrentPage( CTrainingBasePanel *pPanel, bool bGoingBack = false )
+ {
+ AssertMsg( pPanel, "Setting current page to NULL!" );
+
+ pPanel->SetVisible( true );
+
+ if ( !bGoingBack )
+ {
+ pPanel->SetPrevPage( m_pCurrentPagePanel );
+ }
+
+ m_pCurrentPagePanel = pPanel;
+ m_pCurrentPagePanel->InvalidateLayout( false, true );
+
+ InvalidateLayout( true, false );
+ }
+
+ void ShowPrevPage()
+ {
+ CTrainingBasePanel *pPrevPagePanel = m_pCurrentPagePanel->GetPrevPage();
+ if ( pPrevPagePanel )
+ {
+ if ( m_pCurrentPagePanel == pPrevPagePanel )
+ return;
+
+ m_pCurrentPagePanel->SetVisible( false );
+ m_pCurrentPagePanel->OnBackPressed();
+ SetCurrentPage( pPrevPagePanel, true );
+ }
+ else
+ {
+ OnCommand( "cancel" );
+ }
+ }
+
+ void HideCurrentPage()
+ {
+ m_pCurrentPagePanel->SetVisible( false );
+ }
+
+ void BasicTraining_ShowClassSelection()
+ {
+ if ( m_pCurrentPagePanel == m_pBasicTraining_ClassSelectionPanel )
+ return;
+
+ HideCurrentPage();
+ SetCurrentPage( m_pBasicTraining_ClassSelectionPanel );
+ }
+
+ int GetClassFromData( KeyValues *pClassData )
+ {
+ const int iClass = pClassData->GetInt( "class", TF_CLASS_SOLDIER );
+ if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS )
+ {
+ return TF_CLASS_SOLDIER;
+ }
+ return iClass;
+ }
+
+ static void ConfirmDialogCallback( bool bConfirmed, void *pContext )
+ {
+ CTrainingDialog *pDialog = ( CTrainingDialog * )pContext;
+ if ( pDialog )
+ {
+ pDialog->m_bContinue = bConfirmed;
+ pDialog->m_bStartTraining = true;
+ }
+ }
+
+ void ConfirmContinue()
+ {
+ ShowConfirmDialog( "#TR_ContinueTitle", "#TR_ContinueMsg", "#TR_Continue", "#TR_StartOver", &ConfirmDialogCallback, NULL, this );
+ }
+
+ void BasicTraining_Start()
+ {
+ if ( !m_pTrainingData )
+ return;
+
+ KeyValues *pData = m_pTrainingData->FindKey( m_strBasicTrainingClassName.Get() );
+ if ( !pData )
+ return;
+
+ // Override for soldier - if target practice is complete, start from
+ const int iClass = GetClassFromData( pData );
+ int nProgress = Training_GetClassProgress( iClass );
+ if ( iClass == TF_CLASS_SOLDIER && nProgress >= 1 )
+ {
+ ConfirmContinue();
+ return;
+ }
+
+ m_bStartTraining = true;
+ m_bContinue = false;
+ }
+
+ virtual void Think()
+ {
+ if ( !m_bStartTraining )
+ return;
+
+ KeyValues *pData = m_pTrainingData->FindKey( m_strBasicTrainingClassName.Get() );
+ if ( !pData )
+ return;
+
+ // Override map if user has selected to continue
+ const char *pMapName = pData->GetString( "map", NULL );
+ if ( m_bContinue )
+ {
+ pMapName = "tr_dustbowl";
+ }
+
+ if ( pMapName )
+ {
+ const int iClass = GetClassFromData( pData );
+
+ ConVarRef training_class( "training_class" );
+ training_class.SetValue( iClass );
+
+ const char* pMapVideo = pData->GetString( "video", "" );
+ training_map_video.SetValue( pMapVideo );
+
+ // create the command to execute
+ CFmtStr fmtMapCommand( "disconnect\nwait\nwait\n\nprogress_enable\nmap %s\n", pMapName );
+
+ // exec
+ engine->ClientCmd_Unrestricted( fmtMapCommand.Access() );
+ }
+
+ Close();
+ }
+
+ void BasicTraining_ShowClassDetailsPage( const char *pClassName )
+ {
+ if ( m_pCurrentPagePanel == m_pBasicTraining_ClassDetailsPanel )
+ return;
+
+ HideCurrentPage();
+
+ m_pBasicTraining_ClassDetailsPanel->SetClass( pClassName );
+ m_pBasicTraining_ClassDetailsPanel->InvalidateLayout( true, true );
+
+ SetCurrentPage( m_pBasicTraining_ClassDetailsPanel );
+
+ // Cache class
+ m_strBasicTrainingClassName = pClassName;
+ }
+
+ void OfflinePractice_ShowPracticeMode()
+ {
+ if ( m_pCurrentPagePanel == m_pOfflinePractice_ModeSelectionPanel )
+ return;
+
+ HideCurrentPage();
+ SetCurrentPage( m_pOfflinePractice_ModeSelectionPanel );
+ }
+
+ void OfflinePractice_ShowMapSelection()
+ {
+ if ( m_pCurrentPagePanel == m_pOfflinePractice_MapSelectionPanel )
+ return;
+
+ // Pass on the game mode to the map selection panel so it will only show corresponding maps
+ const GameMode_t nGameMode = m_pOfflinePractice_ModeSelectionPanel->GetMode();
+ m_pOfflinePractice_MapSelectionPanel->SetGameMode( nGameMode );
+
+ HideCurrentPage();
+ SetCurrentPage( m_pOfflinePractice_MapSelectionPanel );
+ }
+
+ void OfflinePractice_Start()
+ {
+ // reset server enforced cvars
+ g_pCVar->RevertFlaggedConVars( FCVAR_REPLICATED );
+
+ // Cheats were disabled; revert all cheat cvars to their default values.
+ // This must be done heading into multiplayer games because people can play
+ // demos etc and set cheat cvars with sv_cheats 0.
+ g_pCVar->RevertFlaggedConVars( FCVAR_CHEAT );
+
+ DevMsg( "FCVAR_CHEAT cvars reverted to defaults.\n" );
+
+ if ( m_pOfflinePractice_MapSelectionPanel->DoSetup() )
+ {
+ // create the command to execute
+ CFmtStr1024 fmtMapCommand(
+ "disconnect\nwait\nwait\nmaxplayers %i\n\nprogress_enable\nmap %s\n",
+ m_pOfflinePractice_MapSelectionPanel->GetMaxPlayers(),
+ m_pOfflinePractice_MapSelectionPanel->GetMapName()
+ );
+
+ // exec
+ engine->ClientCmd_Unrestricted( fmtMapCommand.Access() );
+ }
+
+ Close();
+ }
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/ui/training/main.res" );
+
+ SetupButton( "CancelButton", &m_pCancelButton );
+ SetupButton( "BackButton", &m_pBackButton );
+
+ m_pModeSelectionPanel = dynamic_cast< CModeSelectionPanel * >( m_pContainer->FindChildByName( "ModeSelectionPanel" ) ); Assert( m_pModeSelectionPanel );
+ m_pBasicTraining_ClassSelectionPanel = dynamic_cast< CBasicTraining_ClassSelectionPanel * >( m_pContainer->FindChildByName( "BasicTraining_ClassSelectionPanel" ) ); Assert( m_pBasicTraining_ClassSelectionPanel );
+ m_pBasicTraining_ClassDetailsPanel = dynamic_cast< CBasicTraining_ClassDetailsPanel * >( m_pContainer->FindChildByName( "BasicTraining_ClassDetailsPanel" ) ); Assert( m_pBasicTraining_ClassDetailsPanel );
+ m_pOfflinePractice_ModeSelectionPanel = dynamic_cast< COfflinePractice_ModeSelectionPanel * >( m_pContainer->FindChildByName( "OfflinePractice_ModeSelectionPanel" ) ); Assert( m_pOfflinePractice_ModeSelectionPanel );
+ m_pOfflinePractice_MapSelectionPanel = dynamic_cast< COfflinePractice_MapSelectionPanel * >( m_pContainer->FindChildByName( "OfflinePractice_MapSelectionPanel" ) ); Assert( m_pOfflinePractice_MapSelectionPanel );
+ m_pGradientBgPanel = dynamic_cast< ImagePanel * >( m_pContainer->FindChildByName( "GradientBgPanel" ) );
+
+ m_pCurrentPagePanel = m_pModeSelectionPanel;
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+
+ if ( m_pCurrentPagePanel )
+ {
+ m_pCurrentPagePanel->SetVisible( true );
+
+ const bool bFirstPage = m_pCurrentPagePanel->IsFirstPage();
+ if ( m_pBackButton && m_pCancelButton )
+ {
+ m_pBackButton->SetVisible( !bFirstPage );
+
+ int w = m_pContainer->GetWide();
+ const int nBuffer = XRES( 5 );
+
+ int cbx, cby;
+ m_pCancelButton->GetPos( cbx, cby );
+
+ if ( bFirstPage )
+ {
+ m_pCancelButton->SetPos( ( w - m_pCancelButton->GetWide() ) / 2, cby );
+ }
+ else
+ {
+ m_pBackButton->SetPos( w/2 - m_pBackButton->GetWide() - nBuffer, cby );
+ m_pCancelButton->SetPos( w/2 + nBuffer, cby );
+ }
+
+ if ( m_pGradientBgPanel )
+ {
+ m_pGradientBgPanel->SetVisible( m_pCurrentPagePanel->ShouldShowGradient() );
+ }
+ }
+ }
+ }
+
+ virtual void OnKeyCodePressed( KeyCode code )
+ {
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+ if ( code == KEY_ESCAPE )
+ {
+ OnCommand( "cancel" );
+ }
+ else if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B )
+ {
+ OnCommand( "prevpage" );
+ }
+ else if ( code == KEY_ENTER || code == KEY_SPACE || nButtonCode == KEY_XBUTTON_A || nButtonCode == STEAMCONTROLLER_A )
+ {
+ if ( m_pCurrentPagePanel )
+ {
+ m_pCurrentPagePanel->Go();
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+ }
+
+protected:
+ void Close()
+ {
+ SetVisible( false );
+ TFModalStack()->PopModal( this );
+ MarkForDeletion();
+ }
+
+private:
+ EditablePanel *m_pContainer;
+ CModeSelectionPanel *m_pModeSelectionPanel;
+ CBasicTraining_ClassSelectionPanel *m_pBasicTraining_ClassSelectionPanel;
+ CBasicTraining_ClassDetailsPanel *m_pBasicTraining_ClassDetailsPanel;
+ COfflinePractice_ModeSelectionPanel *m_pOfflinePractice_ModeSelectionPanel;
+ COfflinePractice_MapSelectionPanel *m_pOfflinePractice_MapSelectionPanel;
+ CTrainingBasePanel *m_pCurrentPagePanel;
+ CTrainingBasePanel *m_pPrevPagePanel;
+ CExButton *m_pCancelButton;
+ CExButton *m_pBackButton;
+ ImagePanel *m_pGradientBgPanel;
+ KeyValues *m_pTrainingData;
+ CUtlString m_strBasicTrainingClassName;
+ bool m_bStartTraining;
+ bool m_bContinue;
+};
+
+static DHANDLE<CTrainingDialog> g_pTrainingDialog;
+
+//------------------------------------------------------------------------------------------------------
+
+void CL_ShowTrainingDialog( const CCommand &args )
+{
+ if ( g_pTrainingDialog.Get() == NULL )
+ {
+ IViewPortPanel *pMMOverride = ( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) );
+ g_pTrainingDialog = new CTrainingDialog( (CHudMainMenuOverride*)pMMOverride );
+ g_pTrainingDialog->InvalidateLayout( true, true );
+ }
+ g_pTrainingDialog->Show();
+}
+
+//------------------------------------------------------------------------------------------------------
+
+CON_COMMAND( cl_training_class_unlock_all, "Unlock all training" )
+{
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i )
+ {
+ Training_MarkClassComplete( i, 100 );
+ }
+}
+
+//------------------------------------------------------------------------------------------------------
+
+#ifdef _DEBUG
+CON_COMMAND( training_set, 0 )
+{
+ if ( args.ArgC() != 3 )
+ {
+ Warning( "Not enough arguments\n" );
+ return;
+ }
+
+ Training_MarkClassComplete( atoi( args[1] ), atoi( args[2] ) );
+}
+#endif
+
+//------------------------------------------------------------------------------------------------------
+
+static ConCommand training_showdlg( "training_showdlg", &CL_ShowTrainingDialog, "Displays the training dialog." ); \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_vgui_video.cpp b/game/client/tf/vgui/tf_vgui_video.cpp
new file mode 100644
index 0000000..35945f8
--- /dev/null
+++ b/game/client/tf/vgui/tf_vgui_video.cpp
@@ -0,0 +1,112 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: VGUI panel which can play back video, in-engine
+//
+//=============================================================================
+
+#include "cbase.h"
+#include <vgui/IVGui.h>
+#include <vgui/ISurface.h>
+#include <KeyValues.h>
+#include "vgui_video.h"
+#include "tf_vgui_video.h"
+#include "engine/IEngineSound.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+DECLARE_BUILD_FACTORY( CTFVideoPanel );
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CTFVideoPanel::CTFVideoPanel( vgui::Panel *parent, const char *panelName ) : VideoPanel( 0, 0, 50, 50 )
+{
+ SetParent( parent );
+ SetProportional( true );
+ SetKeyBoardInputEnabled( false );
+
+ SetBlackBackground( false );
+
+ m_flStartAnimDelay = 0.0f;
+ m_flEndAnimDelay = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CTFVideoPanel::~CTFVideoPanel()
+{
+ ReleaseVideo();
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFVideoPanel::ReleaseVideo()
+{
+ enginesound->NotifyEndMoviePlayback();
+
+ // Destroy any previously allocated video
+ if ( g_pVideo && m_VideoMaterial != NULL )
+ {
+ g_pVideo->DestroyVideoMaterial( m_VideoMaterial );
+ m_VideoMaterial = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFVideoPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ SetExitCommand( inResourceData->GetString( "command", "" ) );
+ m_flStartAnimDelay = inResourceData->GetFloat( "start_delay", 0.0 );
+ m_flEndAnimDelay = inResourceData->GetFloat( "end_delay", 0.0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFVideoPanel::GetPanelPos( int &xpos, int &ypos )
+{
+ vgui::ipanel()->GetAbsPos( GetVPanel(), xpos, ypos );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFVideoPanel::OnVideoOver()
+{
+ BaseClass::OnVideoOver();
+ PostMessage( GetParent(), new KeyValues( "IntroFinished" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFVideoPanel::OnClose()
+{
+ // Fire an exit command if we're asked to do so
+ if ( m_szExitCommand[0] )
+ {
+ engine->ClientCmd( m_szExitCommand );
+ }
+
+ // intentionally skipping VideoPanel::OnClose()
+ EditablePanel::OnClose();
+
+ SetVisible( false );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFVideoPanel::Shutdown()
+{
+ OnClose();
+ ReleaseVideo();
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_vgui_video.h b/game/client/tf/vgui/tf_vgui_video.h
new file mode 100644
index 0000000..63b93e0
--- /dev/null
+++ b/game/client/tf/vgui/tf_vgui_video.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: VGUI panel which can play back video, in-engine
+//
+//=============================================================================
+
+#ifndef TF_VGUI_VIDEO_H
+#define TF_VGUI_VIDEO_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_video.h"
+
+class CTFVideoPanel : public VideoPanel
+{
+ DECLARE_CLASS_SIMPLE( CTFVideoPanel, VideoPanel );
+public:
+
+ CTFVideoPanel( vgui::Panel *parent, const char *panelName );
+ ~CTFVideoPanel();
+
+ virtual void OnClose();
+ virtual void OnKeyCodePressed( vgui::KeyCode code ){}
+ virtual void ApplySettings( KeyValues *inResourceData );
+
+ virtual void GetPanelPos( int &xpos, int &ypos );
+ virtual void Shutdown();
+
+ float GetStartDelay(){ return m_flStartAnimDelay; }
+ float GetEndDelay(){ return m_flEndAnimDelay; }
+
+protected:
+ virtual void ReleaseVideo();
+ virtual void OnVideoOver();
+
+private:
+ float m_flStartAnimDelay;
+ float m_flEndAnimDelay;
+};
+
+#endif // TF_VGUI_VIDEO_H
diff --git a/game/client/tf/vgui/tf_viewport.cpp b/game/client/tf/vgui/tf_viewport.cpp
new file mode 100644
index 0000000..bf8e66a
--- /dev/null
+++ b/game/client/tf/vgui/tf_viewport.cpp
@@ -0,0 +1,482 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Client DLL VGUI2 Viewport
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#pragma warning( disable : 4800 ) // disable forcing int to bool performance warning
+
+// VGUI panel includes
+#include <vgui_controls/Panel.h>
+#include <vgui/ISurface.h>
+#include <KeyValues.h>
+#include <vgui/Cursor.h>
+#include <vgui/IScheme.h>
+#include <vgui/IVGui.h>
+#include <vgui/ILocalize.h>
+#include <vgui/VGUI.h>
+#include "tier0/icommandline.h"
+
+// client dll/engine defines
+#include "hud.h"
+#include <voice_status.h>
+
+// viewport definitions
+#include <baseviewport.h>
+#include "tf_viewport.h"
+#include "tf_teammenu.h"
+
+#include "vguicenterprint.h"
+#include "text_message.h"
+#include "tf_classmenu.h"
+
+#include "tf_textwindow.h"
+#include "tf_clientscoreboard.h"
+#include "tf_spectatorgui.h"
+#include "intromenu.h"
+#include "tf_intromenu.h"
+
+#include "tf_controls.h"
+#include "tf_mapinfomenu.h"
+#include "tf_roundinfo.h"
+
+#include "item_pickup_panel.h"
+#include "character_info_panel.h"
+#include "tf_hud_arena_winpanel.h"
+#include "tf_arenateammenu.h"
+#include "tf_hud_pve_winpanel.h"
+#include "hud_chat.h"
+#include "tf_giveawayitempanel.h"
+#if defined( REPLAY_ENABLED )
+#include "replay/vgui/replaybrowsermainpanel.h"
+#endif
+#include "clientmode_tf.h"
+#include "ienginevgui.h"
+#include "tf_hud_mainmenuoverride.h"
+#include "c_tf_objective_resource.h"
+
+#include "quest_log_panel.h"
+
+//#include "tf_overview.h"
+
+/*
+CON_COMMAND( spec_help, "Show spectator help screen")
+{
+ if ( gViewPortInterface )
+ gViewPortInterface->ShowPanel( PANEL_INFO, true );
+}
+
+CON_COMMAND( spec_menu, "Activates spectator menu")
+{
+ bool bShowIt = true;
+
+ C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
+
+ if ( pPlayer && !pPlayer->IsObserver() )
+ return;
+
+ if ( args.ArgC() == 2 )
+ {
+ bShowIt = atoi( args[ 1 ] ) == 1;
+ }
+
+ if ( gViewPortInterface )
+ gViewPortInterface->ShowPanel( PANEL_SPECMENU, bShowIt );
+}
+*/
+
+
+CON_COMMAND( showmapinfo, "Show map info panel" )
+{
+ if ( !gViewPortInterface )
+ return;
+
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ // don't let the player open the team menu themselves until they're a spectator or they're on a regular team and have picked a class
+ if ( pPlayer )
+ {
+ if ( ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) ||
+ ( ( pPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) && ( pPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) )
+ {
+ // close all the other panels that could be open
+ gViewPortInterface->ShowPanel( PANEL_TEAM, false );
+ gViewPortInterface->ShowPanel( PANEL_ARENA_TEAM, false );
+ gViewPortInterface->ShowPanel( PANEL_ARENA_WIN, false );
+ gViewPortInterface->ShowPanel( PANEL_PVE_WIN, false );
+ gViewPortInterface->ShowPanel( PANEL_CLASS_RED, false );
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, false );
+ gViewPortInterface->ShowPanel( PANEL_INTRO, false );
+ gViewPortInterface->ShowPanel( PANEL_ROUNDINFO, false );
+ gViewPortInterface->ShowPanel( PANEL_MAPINFO, true );
+ gViewPortInterface->ShowPanel( PANEL_GIVEAWAY_ITEM, false );
+ }
+ }
+}
+
+CON_COMMAND( changeteam, "Choose a new team" )
+{
+ if ( !gViewPortInterface )
+ return;
+
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ // don't let the player open the team menu themselves until they're on a team
+ if ( pPlayer && pPlayer->CanShowTeamMenu() )
+ {
+ if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
+ {
+ gViewPortInterface->ShowPanel( PANEL_ARENA_TEAM, true );
+ }
+ else
+ {
+ gViewPortInterface->ShowPanel( PANEL_TEAM, true );
+ }
+ }
+}
+
+CON_COMMAND( changeclass, "Choose a new class" )
+{
+ if ( !gViewPortInterface )
+ return;
+
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pPlayer && pPlayer->CanShowClassMenu() )
+ {
+ if ( TFGameRules() && TFGameRules()->IsInArenaMode() && pPlayer->IsAlive() == true )
+ {
+ if ( pPlayer->GetTeamNumber() > LAST_SHARED_TEAM && ( tf_arena_force_class.GetBool() == true || TFGameRules()->InStalemate() == true ) )
+ {
+ CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
+
+ if ( pHUDChat )
+ {
+ char szLocalized[100];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find( "#TF_Arena_NoClassChange" ), szLocalized, sizeof(szLocalized) );
+
+ pHUDChat->ChatPrintf( pPlayer->entindex(), CHAT_FILTER_NONE, "%s ", szLocalized );
+ }
+
+ return;
+ }
+ }
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( !TFGameRules()->InSetup() )
+ {
+ CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
+
+ if ( pHUDChat )
+ {
+ char szLocalized[100];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find( "#TF_MVM_NoClassChangeAfterSetup" ), szLocalized, sizeof(szLocalized) );
+
+ pHUDChat->ChatPrintf( pPlayer->entindex(), CHAT_FILTER_NONE, "%s ", szLocalized );
+ }
+
+ return;
+ }
+ }
+
+ switch( pPlayer->GetTeamNumber() )
+ {
+ case TF_TEAM_RED:
+ gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true );
+ break;
+ case TF_TEAM_BLUE:
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true );
+ break;
+ case TEAM_SPECTATOR:
+ {
+ if( TFGameRules() && TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
+ {
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true );
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+CON_COMMAND( togglescores, "Toggles score panel")
+{
+ if ( !gViewPortInterface )
+ return;
+
+ IViewPortPanel *scoreboard = gViewPortInterface->FindPanelByName( PANEL_SCOREBOARD );
+
+ if ( !scoreboard )
+ return;
+
+ if ( scoreboard->IsVisible() )
+ {
+ gViewPortInterface->ShowPanel( scoreboard, false );
+ GetClientVoiceMgr()->StopSquelchMode();
+ }
+ else
+ {
+ gViewPortInterface->ShowPanel( scoreboard, true );
+ }
+}
+
+CON_COMMAND( show_quest_log, "Show the quest log panel" )
+{
+ if ( !gViewPortInterface )
+ return;
+
+ IViewPortPanel *pQuestLog = gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG );
+
+ if ( !pQuestLog )
+ return;
+
+ if ( pQuestLog->IsVisible() )
+ {
+ gViewPortInterface->ShowPanel( pQuestLog, false );
+ }
+ else
+ {
+ gViewPortInterface->ShowPanel( pQuestLog, true );
+ }
+}
+
+TFViewport::TFViewport()
+{
+ ivgui()->AddTickSignal( GetVPanel(), 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+TFViewport::~TFViewport()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: called when the VGUI subsystem starts up
+// Creates the sub panels and initialises them
+//-----------------------------------------------------------------------------
+void TFViewport::Start( IGameUIFuncs *pGameUIFuncs, IGameEventManager2 * pGameEventManager )
+{
+ BaseClass::Start( pGameUIFuncs, pGameEventManager );
+}
+
+void TFViewport::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ gHUD.InitColors( pScheme );
+
+ SetPaintBackgroundEnabled( false );
+
+ // Precache some font characters for the 360
+ if ( IsX360() || CommandLine()->CheckParm( "-precachefontchars" ) || CommandLine()->CheckParm( "-precachefontintlchars" ) )
+ {
+ const wchar_t *pAllChars = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.!:";
+ const wchar_t *pNumbers = L"0123456789";
+ // Try some tricky characters and some international characters with accents, etc.
+ static const wchar_t IntlChars[] = { 'a', 'b', 'c', 'i', 'o', 'y', 'A', 'B', 'C', 0xbf, 0xc1, 0xd1, 0xd3, 0x00 };
+
+ if ( CommandLine()->CheckParm( "-precachefontintlchars" ) )
+ pAllChars = IntlChars;
+
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardTeamName" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardMedium" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardSmall" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardVerySmall" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "TFFontMedium" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "TFFontSmall" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "HudFontMedium" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "HudFontMediumSmallSecondary" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "HudFontSmall" ), pAllChars );
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "DefaultSmall" ), pAllChars );
+
+ vgui::surface()->PrecacheFontCharacters( pScheme->GetFont( "ScoreboardTeamScore" ), pNumbers );
+ }
+}
+
+//
+// This is the main function of the viewport. Right here is where we create our class menu,
+// team menu, and anything else that we want to turn on and off in the UI.
+//
+IViewPortPanel* TFViewport::CreatePanelByName(const char *szPanelName)
+{
+ IViewPortPanel* newpanel = NULL;
+
+ // overwrite MOD specific panel creation
+
+ if ( Q_strcmp( PANEL_SCOREBOARD, szPanelName ) == 0 )
+ {
+ newpanel = new CTFClientScoreBoardDialog( this );
+ }
+ else if ( Q_strcmp( PANEL_SPECGUI, szPanelName ) == 0 )
+ {
+ newpanel = new CTFSpectatorGUI( this );
+ }
+ else if ( Q_strcmp( PANEL_SPECMENU, szPanelName ) == 0 )
+ {
+// newpanel = new CTFSpectatorGUI( this );
+ }
+ else if ( Q_strcmp( PANEL_OVERVIEW, szPanelName ) == 0 )
+ {
+// newpanel = new CTFMapOverview( this );
+ }
+ else if ( Q_strcmp( PANEL_INFO, szPanelName ) == 0 )
+ {
+ newpanel = new CTFTextWindow( this );
+ }
+ else if ( Q_strcmp( PANEL_MAPINFO, szPanelName ) == 0 )
+ {
+ newpanel = new CTFMapInfoMenu( this );
+ }
+ else if ( Q_strcmp( PANEL_ROUNDINFO, szPanelName ) == 0 )
+ {
+ newpanel = new CTFRoundInfo( this );
+ }
+ else if ( Q_strcmp( PANEL_TEAM, szPanelName ) == 0 )
+ {
+ newpanel = new CTFTeamMenu( this );
+ }
+ else if ( Q_strcmp( PANEL_CLASS_RED, szPanelName ) == 0 )
+ {
+ newpanel = new CTFClassMenu_Red( this );
+ }
+ else if ( Q_strcmp( PANEL_CLASS_BLUE, szPanelName ) == 0 )
+ {
+ newpanel = new CTFClassMenu_Blue( this );
+ }
+ else if ( Q_strcmp( PANEL_INTRO, szPanelName ) == 0 )
+ {
+ newpanel = new CTFIntroMenu( this );
+ }
+ else if ( Q_strcmp( PANEL_ARENA_TEAM, szPanelName ) == 0 )
+ {
+ newpanel = new CTFArenaTeamMenu( this );
+ }
+ else if ( Q_strcmp( PANEL_ARENA_WIN, szPanelName ) == 0 )
+ {
+ newpanel = new CTFArenaWinPanel( this );
+ }
+ else if ( Q_strcmp( PANEL_PVE_WIN, szPanelName ) == 0 )
+ {
+ newpanel = new CTFPVEWinPanel( this );
+ }
+ else if ( Q_strcmp( PANEL_GIVEAWAY_ITEM, szPanelName ) == 0 )
+ {
+ newpanel = new CTFGiveawayItemPanel( this );
+ }
+ else if ( Q_strcmp( PANEL_MAINMENUOVERRIDE, szPanelName ) == 0 )
+ {
+ newpanel = new CHudMainMenuOverride( this );
+ }
+ else if ( V_strcmp( PANEL_QUEST_LOG, szPanelName ) == 0 )
+ {
+ newpanel = new CQuestLogPanel( this );
+ }
+ else
+ {
+ // create a generic base panel, don't add twice
+ newpanel = BaseClass::CreatePanelByName( szPanelName );
+ }
+
+ return newpanel;
+}
+
+void TFViewport::CreateDefaultPanels( void )
+{
+ AddNewPanel( CreatePanelByName( PANEL_MAPINFO ), "PANEL_MAPINFO" );
+ AddNewPanel( CreatePanelByName( PANEL_TEAM ), "PANEL_TEAM" );
+ AddNewPanel( CreatePanelByName( PANEL_CLASS_RED ), "PANEL_CLASS_RED" );
+ AddNewPanel( CreatePanelByName( PANEL_CLASS_BLUE ), "PANEL_CLASS_BLUE" );
+ AddNewPanel( CreatePanelByName( PANEL_INTRO ), "PANEL_INTRO" );
+ AddNewPanel( CreatePanelByName( PANEL_ROUNDINFO ), "PANEL_ROUNDINFO" );
+ AddNewPanel( CreatePanelByName( PANEL_ARENA_WIN ), "PANEL_ARENA_WIN" );
+ AddNewPanel( CreatePanelByName( PANEL_ARENA_TEAM ), "PANEL_ARENA_TEAM" );
+ AddNewPanel( CreatePanelByName( PANEL_PVE_WIN ), "PANEL_PVE_WIN" );
+ AddNewPanel( CreatePanelByName( PANEL_GIVEAWAY_ITEM ), "PANEL_GIVEAWAY_ITEM" );
+ AddNewPanel( CreatePanelByName( PANEL_QUEST_LOG ), "PANEL_QUEST_LOG" );
+
+ CHudMainMenuOverride *pMMOverride = (CHudMainMenuOverride*)CreatePanelByName( PANEL_MAINMENUOVERRIDE );
+ if ( pMMOverride )
+ {
+ AddNewPanel( pMMOverride, "PANEL_MAINMENUOVERRIDE" );
+ pMMOverride->AttachToGameUI();
+ }
+
+ BaseClass::CreateDefaultPanels();
+}
+
+int TFViewport::GetDeathMessageStartHeight( void )
+{
+ int y = YRES(2);
+
+ if ( IsX360() )
+ {
+ y = YRES(36);
+ }
+
+ if ( g_pSpectatorGUI && g_pSpectatorGUI->IsVisible() )
+ {
+ y = YRES(2) + g_pSpectatorGUI->GetTopBarHeight();
+ }
+
+ return y;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void TFViewport::OnScreenSizeChanged( int iOldWide, int iOldTall )
+{
+ BaseClass::OnScreenSizeChanged( iOldWide, iOldTall );
+
+ // we've changed resolution, let's try to figure out if we need to show any of our menus
+ if ( !gViewPortInterface )
+ return;
+
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( pPlayer )
+ {
+ // are we on a team yet?
+ if ( pPlayer->GetTeamNumber() == TEAM_UNASSIGNED )
+ {
+ engine->ClientCmd( "show_motd" );
+ }
+ else if ( ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) && ( pPlayer->m_Shared.GetDesiredPlayerClassIndex() == TF_CLASS_UNDEFINED ) )
+ {
+ if ( tf_arena_force_class.GetBool() == false )
+ {
+ switch( pPlayer->GetTeamNumber() )
+ {
+ case TF_TEAM_RED:
+ gViewPortInterface->ShowPanel( PANEL_CLASS_RED, true );
+ break;
+ case TF_TEAM_BLUE:
+ gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, true );
+ break;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void TFViewport::OnTick()
+{
+ m_pAnimController->UpdateAnimations( gpGlobals->curtime );
+} \ No newline at end of file
diff --git a/game/client/tf/vgui/tf_viewport.h b/game/client/tf/vgui/tf_viewport.h
new file mode 100644
index 0000000..f851cbe
--- /dev/null
+++ b/game/client/tf/vgui/tf_viewport.h
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TF_VIEWPORT_H
+#define TF_VIEWPORT_H
+
+
+#include "tf_shareddefs.h"
+#include "baseviewport.h"
+
+
+using namespace vgui;
+
+namespace vgui
+{
+ class Panel;
+ class Label;
+ class CBitmapImagePanel;
+}
+
+//==============================================================================
+class TFViewport : public CBaseViewport
+{
+
+private:
+ DECLARE_CLASS_SIMPLE( TFViewport, CBaseViewport );
+
+public:
+ TFViewport();
+ ~TFViewport();
+
+ IViewPortPanel* CreatePanelByName(const char *szPanelName);
+ void CreateDefaultPanels( void );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void Start( IGameUIFuncs *pGameUIFuncs, IGameEventManager2 * pGameEventManager );
+
+ int GetDeathMessageStartHeight( void );
+
+ virtual void OnScreenSizeChanged( int iOldWide, int iOldTall );
+
+ virtual void OnTick() OVERRIDE;
+
+private:
+ void CenterWindow( vgui::Frame *win );
+
+};
+
+
+#endif // TF_Viewport_H
diff --git a/game/client/tf/vgui/tf_warinfopanel.cpp b/game/client/tf/vgui/tf_warinfopanel.cpp
new file mode 100644
index 0000000..2458bff
--- /dev/null
+++ b/game/client/tf/vgui/tf_warinfopanel.cpp
@@ -0,0 +1,532 @@
+#include "cbase.h"
+#include "tf_warinfopanel.h"
+#include "econ_controls.h"
+#include "econ_item_inventory.h"
+#include "vgui_avatarimage.h"
+#include "vgui_controls/ProgressBar.h"
+#include <vgui_controls/HTML.h>
+#include "c_tf_player.h"
+#include "econ_ui.h"
+#include "tf_asyncpanel.h"
+#include "item_model_panel.h"
+#include "tf_wardata.h"
+#include "iclientmode.h"
+#include <vgui_controls/AnimationController.h>
+#include "vgui/ISystem.h"
+
+#ifdef STAGING_ONLY
+ConVar tf_war_override_active_state( "tf_war_override_active_state", "-1" );
+#endif
+
+bool IsWarActive( war_definition_index_t nDefIndex )
+{
+#ifdef STAGING_ONLY
+ if ( tf_war_override_active_state.GetInt() != -1 )
+ {
+ if ( tf_war_override_active_state.GetInt() == 0 )
+ return false;
+
+ return true;
+ }
+#endif
+
+ const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nDefIndex );
+ Assert( pWarDef );
+ if ( !pWarDef )
+ return false;
+
+ return pWarDef->IsActive();
+}
+
+template < typename T >
+void SetNestedDialogVariable( Panel *pRoot, const char *pszPanel, const char *pszVariable, T val )
+{
+ EditablePanel *pPanel = pRoot->FindControl<EditablePanel>( pszPanel, true );
+ if ( pPanel )
+ {
+ pPanel->SetDialogVariable( pszVariable, val );
+ }
+}
+
+DECLARE_BUILD_FACTORY( CWarStandingPanel );
+CWarStandingPanel::CWarStandingPanel( Panel* pParent, const char* pszPanelName )
+ : BaseClass( pParent, pszPanelName )
+ , m_flLastUpdateTime( 0.f )
+ , m_pTeam0ProgressBar( NULL )
+ , m_pTeam1ProgressBar( NULL )
+ , m_bNeedsLerp( false )
+{
+ ListenForGameEvent( "global_war_data_updated" );
+}
+
+void CWarStandingPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ LoadControlSettings( "Resource/UI/econ/WarStandingPanel.res" );
+
+ for( int i=0; i<2; ++i )
+ {
+ m_Scores[i].m_pTeamProgressBar = FindControl< ContinuousProgressBar >( CFmtStr( "Team%dProgressBar", i ), true );
+ m_Scores[i].m_pContainerPanel = FindControl< EditablePanel >( CFmtStr( "Team%dContainer", i ), true );
+ m_Scores[i].m_pScoreLabel = FindControl< CExLabel >( CFmtStr( "Team%dScore", i ), true );
+ m_Scores[i].m_pTeamLabel = FindControl< CExLabel >( CFmtStr( "Team%dName", i ), true );
+ }
+}
+
+void CWarStandingPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_strWarName = inResourceData->GetString( "war_name", NULL );
+}
+
+void CWarStandingPanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( Plat_FloatTime() - m_flLastUpdateTime > 30.f )
+ {
+ InvalidateLayout();
+ }
+
+ if ( m_bNeedsLerp )
+ {
+ int nTotal = 0;
+ if ( m_Scores[ 0 ].m_nNewScore != 0 || m_Scores[ 1 ].m_nNewScore != 0 )
+ {
+ nTotal = m_Scores[ 0 ].m_nNewScore + m_Scores[ 1 ].m_nNewScore;
+ }
+
+ float flPercent = GetPercentAnimated();
+
+ for( int i=0; i<2; ++i )
+ {
+ // Lerp flPercent from [0,1] -> [StartScore,EndScore]
+ float flCurrentScore = RemapValClamped( flPercent, 0.f, 1.f, (float)m_Scores[i].m_nLastScore, (float)m_Scores[i].m_nNewScore );
+
+ float flProgressPercent = 0.f;
+ if ( nTotal != 0 )
+ {
+ flProgressPercent = flCurrentScore / (float)nTotal;
+ }
+
+ m_Scores[i].m_pTeamProgressBar->SetProgress( flProgressPercent );
+ m_Scores[i].m_pContainerPanel->SetDialogVariable( CFmtStr( "team%dscore", i ), CFmtStr( "%.0f%%", flProgressPercent * 100.f ) );
+
+ int nScoreXpos = RemapValClamped( flProgressPercent
+ , 0.f
+ , 1.f
+ , m_Scores[i].m_pTeamProgressBar->GetXPos()
+ , m_Scores[i].m_pTeamProgressBar->GetXPos() + m_Scores[i].m_pTeamProgressBar->GetWide() );
+
+ m_Scores[i].m_pScoreLabel->SetPos( nScoreXpos, m_Scores[i].m_pScoreLabel->GetYPos() );
+ }
+
+ if ( flPercent == 1.f )
+ {
+ m_bNeedsLerp = false;
+ }
+ }
+}
+
+void CWarStandingPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( GetPercentAnimated() == 1.f )
+ {
+ m_flLastUpdateTime = Plat_FloatTime();
+ }
+
+ const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByName( m_strWarName );
+ Assert( pWarDef->GetSides().Count() == 2 ); // Needs to be 2 sides to the war for this panel to work!
+ CWarData *pWarData = GetLocalPlayerWarData( pWarDef->GetDefIndex() );
+ war_side_t nAffiliation = INVALID_WAR_SIDE;
+ if ( pWarData )
+ {
+ nAffiliation = pWarData->Obj().affiliation();
+ }
+
+ FOR_EACH_MAP_FAST( pWarDef->GetSides(), i )
+ {
+ uint64 nScore = nScore = GetWarData().GetGlobalSideScore( pWarDef->GetDefIndex(), pWarDef->GetSide( i )->m_nSideIndex );
+
+ m_Scores[ i ].m_nLastScore = m_Scores[ i ].m_nNewScore;
+ m_Scores[ i ].m_nNewScore = nScore;
+
+ if ( m_Scores[ i ].m_pTeamLabel )
+ {
+ m_Scores[ i ].m_pTeamLabel->SetText( pWarDef->GetSide( i )->m_pszLocalizedName );
+ }
+
+ // Check if there's a change to show
+ m_bNeedsLerp |= m_Scores[ i ].m_nLastScore != m_Scores[ i ].m_nNewScore;
+
+ if ( !m_bNeedsLerp )
+ {
+ m_Scores[i].m_pContainerPanel->SetDialogVariable( CFmtStr( "team%dscore", i ), CFmtStr( "%.0f%%", m_Scores[i].m_pTeamProgressBar->GetProgress() * 100.f ) );
+ }
+
+ // Set the "(Your side)" labels
+ SetControlVisible( CFmtStr( "Team%dYourSide", i ), i == nAffiliation, true );
+ }
+}
+
+float CWarStandingPanel::GetPercentAnimated() const
+{
+ float flTimeSinceLastUpdate = Plat_FloatTime() - m_flLastUpdateTime;
+ float flPercent = Bias( RemapValClamped( flTimeSinceLastUpdate, 0.f, 1.2f, 0.f, 1.f ), 0.8f );
+ return flPercent;
+}
+
+void CWarStandingPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "global_war_data_updated" ) )
+ {
+ InvalidateLayout();
+ }
+}
+
+
+DECLARE_BUILD_FACTORY( CWarLandingPanel );
+
+CWarLandingPanel::CWarLandingPanel( Panel *pParent, const char *pszPanelName )
+ : BaseClass( pParent, pszPanelName )
+ , m_flChoseTeamTime( 0.f )
+ , m_nLastKnownSide( INVALID_WAR_SIDE )
+ , m_nPendingSide( INVALID_WAR_SIDE )
+ , m_eJoiningState( NO_ACTION )
+{
+}
+
+void CWarLandingPanel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ KeyValues* pKVConditions = new KeyValues( "conditions" );
+
+ if ( IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) )
+ {
+ pKVConditions->AddSubKey( new KeyValues( "if_war_active" ) );
+ }
+ else
+ {
+ pKVConditions->AddSubKey( new KeyValues( "if_war_over" ) );
+ }
+
+ LoadControlSettings( "Resource/UI/econ/WarJoinPanel.res", NULL, NULL, pKVConditions );
+}
+
+void CWarLandingPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_strSceneAnimName = inResourceData->GetString( "scene_anim_name", NULL );
+ Assert( m_strSceneAnimName.Length() );
+}
+
+void CWarLandingPanel::OnCommand( const char *pCommand )
+{
+ if ( FStrEq( pCommand, "close" ) )
+ {
+ if ( m_eJoiningState == NO_ACTION )
+ {
+ SetVisible( false );
+ }
+ }
+ else if ( V_strncasecmp( pCommand, "join_war", V_strlen( "join_war" ) ) == 0 )
+ {
+ int nSide = V_atoi( &pCommand[ V_strlen( "join_war" ) ] );
+
+ const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( PYRO_VS_HEAVY_WAR_DEF_INDEX );
+
+ if ( !IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) )
+ {
+ DevMsg( "War is inactive" );
+ return;
+ }
+
+ if ( !pWarDef->IsValidSide( nSide ) )
+ {
+ DevMsg( "%d is not a valid side", nSide );
+ return;
+ }
+
+ m_nPendingSide = nSide;
+ Assert( m_eJoiningState == NO_ACTION );
+ m_eJoiningState = CONFIRM_SIDE_SELECTION;
+
+ UpdateUIState();
+ return;
+ }
+ else if ( FStrEq( pCommand, "confirm_team" ) )
+ {
+ if ( !IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) )
+ {
+ DevMsg( "War is inactive" );
+ return;
+ }
+
+ // Join the war!
+ GCSDK::CProtoBufMsg< CGCMsgGC_War_JoinWar > msg( k_EMsgGC_War_JoinWar );
+
+ msg.Body().set_affiliation( m_nPendingSide );
+ msg.Body().set_war_id( PYRO_VS_HEAVY_WAR_DEF_INDEX );
+
+ GCClientSystem()->BSendMessage( msg );
+ m_flChoseTeamTime = Plat_FloatTime();
+ m_eJoiningState = ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE;
+
+ UpdateUIState();
+ return;
+ }
+ else if ( FStrEq( pCommand, "dismiss_joining_result" ) )
+ {
+ m_eJoiningState = NO_ACTION;
+ m_nPendingSide = INVALID_WAR_SIDE;
+ UpdateUIState();
+
+ return;
+ }
+ else if ( FStrEq( "view_update_comic", pCommand ) )
+ {
+ const char* pszComicURL = "http://www.teamfortress.com/theshowdown/";
+
+ if ( steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->IsOverlayEnabled() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( pszComicURL );
+ }
+ else
+ {
+ vgui::system()->ShellExecute( "open", pszComicURL );
+ }
+
+ return;
+ }
+
+ BaseClass::OnCommand( pCommand );
+}
+
+void CWarLandingPanel::OnThink()
+{
+ BaseClass::OnThink();
+
+ if ( ( Plat_FloatTime() - m_flChoseTeamTime ) > 5.f && m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE )
+ {
+ m_eJoiningState = FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION;
+ UpdateUIState();
+ }
+}
+
+#ifdef STAGING_ONLY
+// 12345? Yea, well, we probably will never have 12,345 sides to a war
+ConVar tf_fake_war_side( "tf_fake_war_side", "12345" );
+#endif
+
+void CWarLandingPanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+#ifdef STAGING_ONLY
+ if ( tf_fake_war_side.GetInt() != 12345 )
+ {
+ m_nLastKnownSide = tf_fake_war_side.GetInt();
+ }
+#endif
+
+ UpdateUIState();
+}
+
+
+void CWarLandingPanel::UpdateWarStatus( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
+{
+ if ( pObject->GetTypeID() != CWarData::k_nTypeID )
+ {
+ return;
+ }
+
+ if ( !steamapicontext || !steamapicontext->SteamUser() )
+ {
+ return;
+ }
+
+ // Make sure this is for us
+ CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
+ if ( steamIDOwner != steamID )
+ {
+ return;
+ }
+
+ const CWarData* pWarData = assert_cast< const CWarData* >( pObject );
+ if ( pWarData->Obj().affiliation() != m_nLastKnownSide )
+ {
+ if ( m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE )
+ {
+ m_eJoiningState = SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION;
+ }
+
+ m_nLastKnownSide = pWarData->Obj().affiliation();
+ }
+
+ UpdateUIState();
+}
+
+void CWarLandingPanel::SetVisible( bool bVisible )
+{
+ BaseClass::SetVisible( bVisible );
+
+ EditablePanel* pSceneContainer = FindControl< EditablePanel >( "SceneContainer", true );
+ if ( pSceneContainer )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pSceneContainer, m_strSceneAnimName, false );
+ }
+}
+
+void CWarLandingPanel::UpdateUIState()
+{
+ const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( PYRO_VS_HEAVY_WAR_DEF_INDEX ); // SLAM
+ if ( !pWarDef )
+ return;
+
+ EditablePanel* pMainContainer = FindControl< EditablePanel >( "MainContainer" );
+ if ( pMainContainer )
+ {
+ bool bWarActive = IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX );
+ pMainContainer->SetControlVisible( "WarOverContainer", !bWarActive );
+
+ bool bNeedsToJoin = m_nLastKnownSide == INVALID_WAR_SIDE;
+ pMainContainer->SetControlVisible( "AffiliatedContainer", bWarActive && !bNeedsToJoin && m_eJoiningState == NO_ACTION );
+ pMainContainer->SetControlVisible( "UnaffiliatedContainer", bWarActive && bNeedsToJoin && m_eJoiningState == NO_ACTION );
+
+ bool bHasGCConnection = GCClientSystem()->BConnectedtoGC();
+ pMainContainer->SetControlVisible( "JoinPyroButton", bHasGCConnection, true );
+ pMainContainer->SetControlVisible( "JoinHeavyButton", bHasGCConnection, true );
+ pMainContainer->SetControlVisible( "NoGContainer", !bHasGCConnection, true );
+ }
+
+ static wchar_t wszEndDateOutString[ 128 ];
+ char time_buf[k_RTimeRenderBufferSize];
+ CRTime timeExpire( pWarDef->GetEndDate() );
+ timeExpire.SetToGMT( false );
+ timeExpire.Render( time_buf );
+ wchar_t wszTemp[ 1024 ];
+ V_UTF8ToUnicode( time_buf, wszTemp, sizeof(wszTemp) );
+
+ const wchar_t *wpszEndDateFormat = g_pVGuiLocalize->Find( IsWarActive( PYRO_VS_HEAVY_WAR_DEF_INDEX ) ? "#TF_War_EndFutureDate" : "#TF_War_EndPastDate" );
+
+ g_pVGuiLocalize->ConstructString_safe( wszEndDateOutString, wpszEndDateFormat, 1, wszTemp );
+ CExLabel* pSceneContainer = FindControl< CExLabel >( "EndDateLabel", true );
+ if ( pSceneContainer )
+ {
+ pSceneContainer->SetText( wszEndDateOutString );
+ }
+
+
+ EditablePanel* pJoiningPopup = FindControl< EditablePanel >( "CommunicatingWithGCPopup", true );
+ if ( !pJoiningPopup )
+ return;
+
+ switch ( m_eJoiningState )
+ {
+ case NO_ACTION:
+ break;
+ case CONFIRM_SIDE_SELECTION:
+ {
+ Assert( m_nPendingSide != INVALID_WAR_SIDE );
+ const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nPendingSide ); // Can be NULL
+ Assert( pChosenSide );
+ if ( !pChosenSide )
+ return;
+
+ static wchar_t wszOutString[ 128 ];
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_ConfirmSideSelection" );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) );
+
+ EditablePanel* pJoining = FindControl< EditablePanel >( "ConfirmSelectionContainer", true );
+ if ( pJoining )
+ {
+ pJoining->SetDialogVariable( "confirm_selection", wszOutString );
+ }
+ }
+ break;
+ case ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE:
+ {
+ Assert( m_nPendingSide != INVALID_WAR_SIDE );
+ const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nPendingSide ); // Can be NULL
+ Assert( pChosenSide );
+ if ( !pChosenSide )
+ return;
+
+ static wchar_t wszOutString[ 128 ];
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_JoiningTeam" );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) );
+
+ EditablePanel* pJoining = FindControl< EditablePanel >( "JoiningContainer", true );
+ if ( pJoining )
+ {
+ pJoining->SetDialogVariable( "joining_team", wszOutString );
+ }
+ }
+
+ break;
+
+ case SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION:
+ {
+ const CWarDefinition::CWarSideDefinition_t* pChosenSide = pWarDef->GetSide( m_nLastKnownSide ); // Can be NULL
+ Assert( pChosenSide );
+ if ( !pChosenSide )
+ return;
+
+ static wchar_t wszOutString[ 128 ];
+ const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#TF_War_JoinedTeam" );
+ g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 1, g_pVGuiLocalize->Find( pChosenSide->m_pszLocalizedName ) );
+ EditablePanel* pJoined = FindControl< EditablePanel >( "TeamJoinedContainer", true );
+ if ( pJoined )
+ {
+ pJoined->SetDialogVariable( "team_joined", wszOutString );
+ }
+ }
+ break;
+
+ case FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION:
+
+ break;
+
+ default:
+ Assert( false );
+ return;
+ }
+
+ pJoiningPopup->SetVisible( m_eJoiningState != NO_ACTION );
+
+ pJoiningPopup->SetControlVisible( "ConfirmSelectionContainer", m_eJoiningState == CONFIRM_SIDE_SELECTION, true );
+ pJoiningPopup->SetControlVisible( "JoiningContainer", m_eJoiningState == ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE, true );
+ pJoiningPopup->SetControlVisible( "TeamJoinedContainer", m_eJoiningState == SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, true );
+ pJoiningPopup->SetControlVisible( "FailedToJoinContainer", m_eJoiningState == FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION, true );
+}
+
+#if defined( STAGING_ONLY )
+CON_COMMAND( tf_war_join_side, "Join a specified war on a specified side" )
+{
+ if ( args.ArgC() < 2 )
+ return;
+
+ war_definition_index_t nWar = atoi( args[1] );
+ const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nWar );
+ if ( pWarDef == NULL)
+ return;
+
+ war_side_t nSide = atoi( args[2] );
+ // Allow INVALID_WAR_SIDE for testing purposes
+ if ( pWarDef->GetSide( nSide ) == NULL && nSide != INVALID_WAR_SIDE )
+ return;
+
+ // Join the war!
+ GCSDK::CProtoBufMsg< CGCMsgGC_War_JoinWar > msg( k_EMsgGC_War_JoinWar );
+
+ msg.Body().set_war_id( nWar );
+ msg.Body().set_affiliation( nSide );
+
+ GCClientSystem()->BSendMessage( msg );
+}
+#endif
diff --git a/game/client/tf/vgui/tf_warinfopanel.h b/game/client/tf/vgui/tf_warinfopanel.h
new file mode 100644
index 0000000..2771b14
--- /dev/null
+++ b/game/client/tf/vgui/tf_warinfopanel.h
@@ -0,0 +1,100 @@
+#ifndef TF_WARINFOPANEL_H
+#define TF_WARINFOPANEL_H
+
+
+#include "vgui_controls/EditablePanel.h"
+#include "tf_wardata.h"
+#include "vgui_controls/ProgressBar.h"
+#include "local_steam_shared_object_listener.h"
+
+using namespace vgui;
+using namespace GCSDK;
+
+class CExLabel;
+
+class CWarStandingPanel : public EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CWarStandingPanel, EditablePanel );
+public:
+ CWarStandingPanel( Panel* pParent, const char* pszPanelname );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+private:
+
+ float GetPercentAnimated() const;
+
+ struct TeamScore_t
+ {
+ TeamScore_t()
+ : m_nLastScore( 0 )
+ , m_nNewScore( 0 )
+ , m_pTeamProgressBar( NULL )
+ , m_pContainerPanel( NULL )
+ , m_pTeamLabel( NULL )
+ , m_pScoreLabel( NULL )
+ {}
+ int m_nLastScore;
+ int m_nNewScore;
+ ContinuousProgressBar *m_pTeamProgressBar;
+ EditablePanel* m_pContainerPanel;
+ CExLabel* m_pTeamLabel;
+ CExLabel* m_pScoreLabel;
+ };
+
+ bool m_bNeedsLerp;
+ TeamScore_t m_Scores[2];
+ float m_flLastUpdateTime;
+ ContinuousProgressBar *m_pTeam0ProgressBar;
+ ContinuousProgressBar *m_pTeam1ProgressBar;
+ CUtlString m_strWarName;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CWarLandingPanel : public EditablePanel, public CLocalSteamSharedObjectListener
+{
+ DECLARE_CLASS_SIMPLE( CWarLandingPanel, EditablePanel );
+public:
+ CWarLandingPanel( Panel *pParent, const char *pszPanelName );
+
+ virtual void ApplySchemeSettings( IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void OnCommand( const char *pCommand ) OVERRIDE;
+ virtual void OnThink() OVERRIDE;
+ virtual void PerformLayout() OVERRIDE;
+
+ virtual void SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) { UpdateWarStatus( steamIDOwner, pObject, eEvent ); }
+ virtual void SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) { UpdateWarStatus( steamIDOwner, pObject, eEvent ); }
+ virtual void SODestroyed( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) { UpdateWarStatus( steamIDOwner, pObject, eEvent ); }
+
+ virtual void SetVisible( bool bVisible ) OVERRIDE;
+
+private:
+
+ enum EJoiningState_t
+ {
+ NO_ACTION = 0,
+ CONFIRM_SIDE_SELECTION,
+ ATTEMPTING_TO_JOIN_AND_WAITING_FOR_RESPONSE,
+ SUCCESS_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION,
+ FAILED_RESPONSE_RECIEVED_WAITING_FOR_USER_CONFIRMATION,
+ };
+
+ void UpdateWarStatus( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent );
+
+ void UpdateUIState();
+
+ float m_flChoseTeamTime;
+ war_side_t m_nPendingSide;
+ war_side_t m_nLastKnownSide;
+ CUtlString m_strSceneAnimName;
+
+ EJoiningState_t m_eJoiningState;
+};
+
+#endif //TF_WARINFOPANEL_H \ No newline at end of file
diff --git a/game/client/tf/vgui/vgui_critpanel.cpp b/game/client/tf/vgui/vgui_critpanel.cpp
new file mode 100644
index 0000000..a58d18b
--- /dev/null
+++ b/game/client/tf/vgui/vgui_critpanel.cpp
@@ -0,0 +1,255 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=====================================================================================//
+
+#include "cbase.h"
+#include "hud.h"
+#include "hudelement.h"
+#include "hud_macros.h"
+#include "hud_numericdisplay.h"
+#include <KeyValues.h>
+#include <vgui/IScheme.h>
+#include <vgui/ISurface.h>
+#include <vgui/ISystem.h>
+#include <vgui_controls/AnimationController.h>
+#include "iclientmode.h"
+#include "tf_shareddefs.h"
+#include <vgui_controls/EditablePanel.h>
+#include <vgui_controls/Label.h>
+#include <vgui_controls/ImagePanel.h>
+#include <vgui/ISurface.h>
+#include <vgui/IImage.h>
+#include <vgui_controls/Label.h>
+#include "VGuiMatSurface/IMatSystemSurface.h"
+
+#include "c_tf_player.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static ConVar cl_showcrit( "cl_showcrit", "0", FCVAR_DEVELOPMENTONLY, "Debug! Draw crit values above the ammo count." );
+
+//-----------------------------------------------------------------------------
+// Purpose: Critical meter panel.
+//-----------------------------------------------------------------------------
+class CCriticalPanel : public CHudElement, public vgui::EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CCriticalPanel, vgui::EditablePanel );
+
+ CCriticalPanel( const char *pElementName );
+ virtual ~CCriticalPanel( void );
+
+ virtual void Reset();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual bool ShouldDraw( void );
+
+protected:
+
+ virtual void OnThink();
+
+private:
+
+ void InitCritData( void );
+
+ // vgui
+ vgui::HFont m_hFont;
+ vgui::Label *m_pTextLabel;
+
+ float m_flNextThink;
+
+ // Critical Data.
+ struct CriticalData_t
+ {
+ float m_flCurrent;
+ float m_flAverage;
+ float m_flLow;
+ float m_flHigh;
+ };
+
+ bool m_bInitData;
+ CriticalData_t m_CritData;
+};
+
+DECLARE_HUDELEMENT( CCriticalPanel );
+
+#define CRITICAL_PANEL_WIDTH 300
+#define CRITICAL_BLEND_WEIGHT 0.1f
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor.
+// Input : *parent - parent VGUI window
+//-----------------------------------------------------------------------------
+CCriticalPanel::CCriticalPanel( const char *pElementName ) : CHudElement( pElementName ), BaseClass( NULL, "CriticalPanel" )
+{
+ Panel *pParent = g_pClientMode->GetViewport();
+ SetParent( pParent );
+ SetScheme( "ClientScheme" );
+
+ SetVisible( false );
+ SetCursor( null );
+
+ SetFgColor( Color( 0, 0, 0, 255 ) );
+ SetPaintBackgroundEnabled( false );
+
+ m_pTextLabel = new vgui::Label( this, "CriticalPanelLabel", "" );
+ m_hFont = 0;
+
+ // Have we initialize the criticl data yet?
+ m_bInitData = false;
+
+ m_flNextThink = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor.
+//-----------------------------------------------------------------------------
+CCriticalPanel::~CCriticalPanel( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCriticalPanel::Reset()
+{
+ m_flNextThink = gpGlobals->curtime + 0.05f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCriticalPanel::InitCritData( void )
+{
+ m_CritData.m_flCurrent = -1.0f;
+ m_CritData.m_flAverage = -1.0f;
+ m_CritData.m_flLow = -1.0f;
+ m_CritData.m_flHigh = -1.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCriticalPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pTextLabel->SetBounds( 0.0f, 0.0f, GetWide(), GetTall() );
+ m_pTextLabel->SetFont( pScheme->GetFont( "DefaultSmall" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CCriticalPanel::ShouldDraw( void )
+{
+ return false;
+
+ if ( ( !cl_showcrit.GetInt() || ( gpGlobals->absoluteframetime <= 0 ) ) &&
+ ( !cl_showcrit.GetInt() ) )
+ {
+ m_bInitData = false;
+ return false;
+ }
+
+ if ( !m_bInitData )
+ {
+ m_bInitData = true;
+ InitCritData();
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void GetCritColor( float flCritMult, unsigned char ucColor[3] )
+{
+ ucColor[0] = 255; ucColor[1] = 255; ucColor[2] = 0;
+
+ if ( flCritMult < 5.0f )
+ {
+ ucColor[1] = 0;
+ }
+ else if ( flCritMult > 5.0f )
+ {
+ ucColor[0] = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCriticalPanel::OnThink()
+{
+ // Get the local TF player.
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pPlayer )
+ return;
+
+ if ( cl_showcrit.GetInt() )
+ {
+ // Get the current critical multiplier.
+ float flCritMult = pPlayer->GetCritMult() * TF_DAMAGE_CRIT_CHANCE;
+ flCritMult *= 100.0f;
+
+ if ( m_CritData.m_flAverage < 0.0f )
+ {
+ // Initial data.
+ m_CritData.m_flCurrent = flCritMult;
+ m_CritData.m_flAverage = flCritMult;
+ m_CritData.m_flLow = m_CritData.m_flAverage;
+ m_CritData.m_flHigh = m_CritData.m_flAverage;
+ }
+ else
+ {
+ // Average over time.
+ m_CritData.m_flCurrent = flCritMult;
+ m_CritData.m_flAverage *= ( 1.0f - CRITICAL_BLEND_WEIGHT ) ;
+ m_CritData.m_flAverage += ( flCritMult * CRITICAL_BLEND_WEIGHT );
+ }
+
+ // Adjust for highs and lows.
+ m_CritData.m_flLow = MIN( m_CritData.m_flLow, flCritMult );
+ m_CritData.m_flHigh = MAX( m_CritData.m_flHigh, flCritMult );
+
+ unsigned char ucColor[3];
+ GetCritColor( flCritMult, ucColor );
+ m_pTextLabel->SetFgColor( Color( ucColor[0], ucColor[1], ucColor[2], 255 ) );
+
+ char szCriticalText[256];
+ //Q_snprintf( szCriticalText, sizeof( szCriticalText ), "Crit: %3.2f (A:%3.2f, L:%3.2f, H:%3.2f)",
+ // m_CritData.m_flCurrent, m_CritData.m_flAverage, m_CritData.m_flLow, m_CritData.m_flHigh );
+ Q_snprintf( szCriticalText, sizeof( szCriticalText ), "Crit Chance: %3.2f", m_CritData.m_flCurrent );
+
+ m_pTextLabel->SetText( szCriticalText );
+
+ m_flNextThink = gpGlobals->curtime + 0.01f;
+ }
+ else
+ {
+ m_flNextThink = gpGlobals->curtime + 0.1f;
+ }
+}
+
+#if 0
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+//-----------------------------------------------------------------------------
+void CCriticalPanel::Paint()
+{
+ if ( cl_showcrit.GetInt() )
+ {
+ unsigned char ucColor[3];
+ GetCritColor( m_CritData.m_flCurrent, ucColor );
+ g_pMatSystemSurface->DrawColoredText( m_hFont, 0, 2, ucColor[0], ucColor[1], ucColor[2], 255,
+ "%2.2f (Avg:%2.2f, Low:%2.2f, High:%2.2f)",
+ m_CritData.m_flCurrent, m_CritData.m_flAverage, m_CritData.m_flLow, m_CritData.m_flHigh );
+ }
+}
+#endif \ No newline at end of file
diff --git a/game/client/tf/vgui/vgui_pda_panel.cpp b/game/client/tf/vgui/vgui_pda_panel.cpp
new file mode 100644
index 0000000..28ae408
--- /dev/null
+++ b/game/client/tf/vgui/vgui_pda_panel.cpp
@@ -0,0 +1,336 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "c_vguiscreen.h"
+#include <vgui/IVGui.h>
+#include "ienginevgui.h"
+#include "vgui_bitmapimage.h"
+#include "vgui_bitmappanel.h"
+#include "vgui_controls/Label.h"
+#include "tf_weapon_pda.h"
+#include "vgui/ISurface.h"
+#include "tf_controls.h"
+#include "c_tf_player.h"
+#include "tf_weapon_invis.h"
+#include <vgui_controls/RadioButton.h>
+#include "clientmode.h"
+#include <vgui_controls/ProgressBar.h>
+#include <vgui_controls/CircularProgressBar.h>
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Control screen
+//-----------------------------------------------------------------------------
+class CPDAPanel : public CVGuiScreenPanel
+{
+ DECLARE_CLASS( CPDAPanel, CVGuiScreenPanel );
+
+public:
+ CPDAPanel( vgui::Panel *parent, const char *panelName );
+ ~CPDAPanel();
+ virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData );
+ virtual void OnTick();
+
+protected:
+ C_BaseCombatWeapon *GetOwningWeapon();
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel, "pda_panel" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel::CPDAPanel( vgui::Panel *parent, const char *panelName )
+: BaseClass( parent, "CPDAPanel", vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/PDAControlPanelScheme.res", "TFBase" ) )
+{
+}
+
+CPDAPanel::~CPDAPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Initialization
+//-----------------------------------------------------------------------------
+bool CPDAPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData )
+{
+ // Make sure we get ticked...
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ if (!BaseClass::Init(pKeyValues, pInitData))
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Returns the object it's attached to
+//-----------------------------------------------------------------------------
+C_BaseCombatWeapon *CPDAPanel::GetOwningWeapon()
+{
+ C_BaseEntity *pScreenEnt = GetEntity();
+ if (!pScreenEnt)
+ return NULL;
+
+ C_BaseEntity *pOwner = pScreenEnt->GetOwnerEntity();
+ if (!pOwner)
+ return NULL;
+
+ C_BaseViewModel *pViewModel = dynamic_cast< C_BaseViewModel * >( pOwner );
+ if ( !pViewModel )
+ return NULL;
+
+ return pViewModel->GetOwningWeapon();
+}
+
+//-----------------------------------------------------------------------------
+// Frame-based update
+//-----------------------------------------------------------------------------
+void CPDAPanel::OnTick()
+{
+ BaseClass::OnTick();
+
+ SetVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+// Engineer Destroy PDA
+//-----------------------------------------------------------------------------
+
+class CPDAPanel_Engineer_Destroy : public CPDAPanel
+{
+ DECLARE_CLASS( CPDAPanel_Engineer_Destroy, CPDAPanel );
+
+public:
+ CPDAPanel_Engineer_Destroy( vgui::Panel *parent, const char *panelName );
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Engineer_Destroy, "pda_panel_engineer_destroy" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel_Engineer_Destroy::CPDAPanel_Engineer_Destroy( vgui::Panel *parent, const char *panelName )
+: CPDAPanel( parent, "CPDAPanel_Engineer_Destroy" )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Engineer Build PDA
+//-----------------------------------------------------------------------------
+
+class CPDAPanel_Engineer_Build : public CPDAPanel
+{
+ DECLARE_CLASS( CPDAPanel_Engineer_Build, CPDAPanel );
+
+public:
+ CPDAPanel_Engineer_Build( vgui::Panel *parent, const char *panelName );
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Engineer_Build, "pda_panel_engineer_build" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel_Engineer_Build::CPDAPanel_Engineer_Build( vgui::Panel *parent, const char *panelName )
+: CPDAPanel( parent, "CPDAPanel_Engineer" )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Spy PDA
+//-----------------------------------------------------------------------------
+
+class CPDAPanel_Spy : public CPDAPanel
+{
+ DECLARE_CLASS( CPDAPanel_Spy, CPDAPanel );
+
+public:
+ CPDAPanel_Spy( vgui::Panel *parent, const char *panelName );
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy, "pda_panel_spy" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel_Spy::CPDAPanel_Spy( vgui::Panel *parent, const char *panelName )
+: CPDAPanel( parent, "CPDAPanel_Spy" )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Spy Invis PDA
+//-----------------------------------------------------------------------------
+
+class CPDAPanel_Spy_Invis : public CPDAPanel
+{
+ DECLARE_CLASS( CPDAPanel_Spy_Invis, CPDAPanel );
+
+public:
+ CPDAPanel_Spy_Invis( vgui::Panel *parent, const char *panelName );
+
+ virtual void OnTick();
+
+private:
+
+ ProgressBar *m_pInvisProgress;
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis, "pda_panel_spy_invis" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel_Spy_Invis::CPDAPanel_Spy_Invis( vgui::Panel *parent, const char *panelName )
+: CPDAPanel( parent, "CPDAPanel_Spy_Invis" )
+{
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ m_pInvisProgress = new ProgressBar( this, "InvisProgress" );
+}
+
+//-----------------------------------------------------------------------------
+// Update the progress bar with how much invis we have left
+//-----------------------------------------------------------------------------
+void CPDAPanel_Spy_Invis::OnTick( void )
+{
+ C_BaseCombatWeapon *pInvisWeapon = GetOwningWeapon();
+
+ if ( !pInvisWeapon )
+ return;
+
+ C_TFPlayer *pPlayer = ToTFPlayer( pInvisWeapon->GetOwner() );
+
+ if ( pPlayer && !pPlayer->IsDormant() )
+ {
+ if ( m_pInvisProgress )
+ {
+ float flMeter = pPlayer->m_Shared.GetSpyCloakMeter();
+ m_pInvisProgress->SetProgress( flMeter / 100.0f );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Spy Invis PDA panel for the pocketwatch
+//-----------------------------------------------------------------------------
+class CPDAPanel_Spy_Invis_Pocket : public CPDAPanel
+{
+ DECLARE_CLASS( CPDAPanel_Spy_Invis_Pocket, CPDAPanel );
+
+public:
+ CPDAPanel_Spy_Invis_Pocket( vgui::Panel *parent, const char *panelName );
+
+ virtual void OnTick();
+
+protected:
+ ProgressBar *m_pInvisProgress;
+ float m_flPrevProgress;
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis_Pocket, "pda_panel_spy_invis_pocket" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel_Spy_Invis_Pocket::CPDAPanel_Spy_Invis_Pocket( vgui::Panel *parent, const char *panelName )
+ : CPDAPanel( parent, "CPDAPanel_Spy_Invis_Pocket" )
+{
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ CircularProgressBar* pCircularProgressBar = new CircularProgressBar( this, "InvisProgress" );
+ pCircularProgressBar->SetReverseProgress( true );
+ m_pInvisProgress = pCircularProgressBar;
+ m_flPrevProgress = -1;
+}
+
+//-----------------------------------------------------------------------------
+// Update the progress bar with how much invis we have left
+//-----------------------------------------------------------------------------
+void CPDAPanel_Spy_Invis_Pocket::OnTick( void )
+{
+ C_BaseCombatWeapon *pInvisWeapon = GetOwningWeapon();
+
+ if ( !pInvisWeapon )
+ return;
+
+ C_TFPlayer *pPlayer = ToTFPlayer( pInvisWeapon->GetOwner() );
+
+ if ( pPlayer && !pPlayer->IsDormant() )
+ {
+ if ( m_pInvisProgress )
+ {
+ float flMeter = pPlayer->m_Shared.GetSpyCloakMeter();
+ if ( m_flPrevProgress != flMeter )
+ {
+ m_flPrevProgress = flMeter;
+
+ if ( flMeter == 100.f )
+ {
+ m_pInvisProgress->SetFgColor( COLOR_GREEN );
+ }
+ else if ( flMeter < 40.f )
+ {
+ m_pInvisProgress->SetFgColor( COLOR_RED );
+ }
+ else
+ {
+ m_pInvisProgress->SetFgColor( COLOR_YELLOW );
+ }
+
+ m_pInvisProgress->SetProgress( flMeter / 100.0f );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Spy Invis PDA panel for the TTG pocketwatch
+//-----------------------------------------------------------------------------
+class CPDAPanel_Spy_Invis_Pocket_TTG : public CPDAPanel_Spy_Invis_Pocket
+{
+ DECLARE_CLASS( CPDAPanel_Spy_Invis_Pocket_TTG, CPDAPanel_Spy_Invis_Pocket );
+
+public:
+ CPDAPanel_Spy_Invis_Pocket_TTG( vgui::Panel *parent, const char *panelName );
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis_Pocket_TTG, "pda_panel_spy_invis_pocket_ttg" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel_Spy_Invis_Pocket_TTG::CPDAPanel_Spy_Invis_Pocket_TTG( vgui::Panel *parent, const char *panelName )
+: CPDAPanel_Spy_Invis_Pocket( parent, "CPDAPanel_Spy_Invis_Pocket_TTG" )
+{
+ dynamic_cast<CircularProgressBar*>(m_pInvisProgress)->SetStartSegment( 7 ); // Do the pellet first.
+}
+
+//-----------------------------------------------------------------------------
+// Spy Invis PDA panel for the Hitman pocketwatch
+//-----------------------------------------------------------------------------
+
+class CPDAPanel_Spy_Invis_Pocket_HM : public CPDAPanel_Spy_Invis_Pocket
+{
+ DECLARE_CLASS( CPDAPanel_Spy_Invis_Pocket_HM, CPDAPanel_Spy_Invis_Pocket );
+
+public:
+ CPDAPanel_Spy_Invis_Pocket_HM( vgui::Panel *parent, const char *panelName );
+};
+
+DECLARE_VGUI_SCREEN_FACTORY( CPDAPanel_Spy_Invis_Pocket_HM, "pda_panel_spy_invis_pocket_hm" );
+
+//-----------------------------------------------------------------------------
+// Constructor:
+//-----------------------------------------------------------------------------
+CPDAPanel_Spy_Invis_Pocket_HM::CPDAPanel_Spy_Invis_Pocket_HM( vgui::Panel *parent, const char *panelName )
+ : CPDAPanel_Spy_Invis_Pocket( parent, "CPDAPanel_Spy_Invis_Pocket_HM" )
+{
+}
diff --git a/game/client/tf/vgui/vgui_rootpanel_tf.cpp b/game/client/tf/vgui/vgui_rootpanel_tf.cpp
new file mode 100644
index 0000000..fbd0768
--- /dev/null
+++ b/game/client/tf/vgui/vgui_rootpanel_tf.cpp
@@ -0,0 +1,160 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+#include "cbase.h"
+#include "vgui_int.h"
+#include "ienginevgui.h"
+#include "vgui_rootpanel_tf.h"
+#include "vgui/IVGui.h"
+#include "tier2/fileutils.h"
+#include "icommandline.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+C_TFRootPanel *g_pRootPanel = NULL;
+
+static ConVar tf_ui_version( "tf_ui_version", "1", FCVAR_DEVELOPMENTONLY );
+
+extern const char *COM_GetModDirectory();
+
+void CheckCustomModSearchPaths()
+{
+ const char *pszCustomPathID = "custom_mod";
+
+ CUtlVector< CUtlString > searchPaths;
+ GetSearchPath( searchPaths, pszCustomPathID );
+
+ FOR_EACH_VEC( searchPaths, i )
+ {
+ const char *pszSearchPath = searchPaths[i].String();
+ // check each path for version file
+ char szVersionFile[MAX_PATH];
+ V_ComposeFileName( pszSearchPath, "info.vdf", szVersionFile, sizeof( szVersionFile ) );
+ KeyValuesAD versionKV( pszCustomPathID );
+ if ( versionKV->LoadFromFile( g_pFullFileSystem, szVersionFile ) )
+ {
+ // mod must declare this ConVar
+ if ( tf_ui_version.GetInt() == versionKV->GetInt( "ui_version" ) )
+ {
+ continue;
+ }
+
+ DevMsg( "'ui_version' mismatch. expected version %d. Removed search path '%s' from all pathIDs.\n", tf_ui_version.GetInt(), pszSearchPath );
+ // remove from all path ids
+ g_pFullFileSystem->RemoveSearchPath( pszSearchPath, pszCustomPathID );
+ g_pFullFileSystem->RemoveSearchPath( pszSearchPath, "game" );
+ g_pFullFileSystem->RemoveSearchPath( pszSearchPath, "mod" );
+ }
+ else
+ {
+ DevMsg( "missing 'info.vdf'. Removed search path '%s' from '%s' pathID.\n", pszSearchPath, pszCustomPathID );
+ g_pFullFileSystem->RemoveSearchPath( pszSearchPath, pszCustomPathID );
+ }
+ }
+
+ // only allow to load loose files when using insecure mode
+ if ( CommandLine()->FindParm( "-insecure" ) )
+ {
+ // allow lose files in these search paths
+ g_pFullFileSystem->AddSearchPath( "tf", "vgui" );
+ g_pFullFileSystem->AddSearchPath( "hl2", "vgui" );
+ g_pFullFileSystem->AddSearchPath( "platform", "vgui" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Global functions.
+//-----------------------------------------------------------------------------
+void VGUI_CreateClientDLLRootPanel( void )
+{
+ // do this before creating any vgui panels
+ CheckCustomModSearchPaths();
+
+ g_pRootPanel = new C_TFRootPanel( enginevgui->GetPanel( PANEL_CLIENTDLL ) );
+}
+
+void VGUI_DestroyClientDLLRootPanel( void )
+{
+ g_pRootPanel->MarkForDeletion();
+ g_pRootPanel = NULL;
+}
+
+vgui::VPANEL VGui_GetClientDLLRootPanel( void )
+{
+ return g_pRootPanel->GetVPanel();
+}
+
+
+//-----------------------------------------------------------------------------
+// C_TFRootPanel implementation.
+//-----------------------------------------------------------------------------
+C_TFRootPanel::C_TFRootPanel( vgui::VPANEL parent )
+ : BaseClass( NULL, "TF Root Panel" )
+{
+ SetParent( parent );
+ SetPaintEnabled( false );
+ SetPaintBorderEnabled( false );
+ SetPaintBackgroundEnabled( false );
+
+ // This panel does post child painting
+ SetPostChildPaintEnabled( true );
+
+ // Make it screen sized
+ SetBounds( 0, 0, ScreenWidth(), ScreenHeight() );
+
+ // Ask for OnTick messages
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+C_TFRootPanel::~C_TFRootPanel( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void C_TFRootPanel::PostChildPaint()
+{
+ BaseClass::PostChildPaint();
+
+ // Draw all panel effects
+ RenderPanelEffects();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For each panel effect, check if it wants to draw and draw it on
+// this panel/surface if so
+//-----------------------------------------------------------------------------
+void C_TFRootPanel::RenderPanelEffects( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void C_TFRootPanel::OnTick( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset effects on level load/shutdown
+//-----------------------------------------------------------------------------
+void C_TFRootPanel::LevelInit( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void C_TFRootPanel::LevelShutdown( void )
+{
+}
+
diff --git a/game/client/tf/vgui/vgui_rootpanel_tf.h b/game/client/tf/vgui/vgui_rootpanel_tf.h
new file mode 100644
index 0000000..b1b8c65
--- /dev/null
+++ b/game/client/tf/vgui/vgui_rootpanel_tf.h
@@ -0,0 +1,56 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef VGUI_ROOTPANEL_TF_H
+#define VGUI_ROOTPANEL_TF_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/EditablePanel.h>
+#include "utlvector.h"
+
+
+class CPanelEffect;
+
+
+// Serial under of effect, for safe lookup
+typedef unsigned int EFFECT_HANDLE;
+
+//-----------------------------------------------------------------------------
+// Purpose: Sits between engine and client .dll panels
+// Responsible for drawing screen overlays
+//-----------------------------------------------------------------------------
+class C_TFRootPanel : public vgui::Panel
+{
+ typedef vgui::Panel BaseClass;
+public:
+ C_TFRootPanel( vgui::VPANEL parent );
+ virtual ~C_TFRootPanel( void );
+
+ // Draw Panel effects here
+ virtual void PostChildPaint();
+
+ // Clear list of Panel Effects
+ virtual void LevelInit( void );
+ virtual void LevelShutdown( void );
+
+ // Run effects and let them decide whether to remove themselves
+ void OnTick( void );
+
+private:
+
+ // Render all panel effects
+ void RenderPanelEffects( void );
+
+ // List of current panel effects
+ CUtlVector< CPanelEffect *> m_Effects;
+};
+
+
+#endif // VGUI_ROOTPANEL_TF_H
diff --git a/game/client/tf/vgui/vgui_rotation_slider.cpp b/game/client/tf/vgui/vgui_rotation_slider.cpp
new file mode 100644
index 0000000..8fc8928
--- /dev/null
+++ b/game/client/tf/vgui/vgui_rotation_slider.cpp
@@ -0,0 +1,74 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include <vgui/MouseCode.h>
+#include "vgui_rotation_slider.h"
+
+
+CRotationSlider::CRotationSlider( vgui::Panel *pParent, const char *pName ) :
+ BaseClass( pParent, pName )
+{
+ AddActionSignalTarget( this );
+ SetRange( -180, 180 );
+ SetTickCaptions("-180", "180");
+ SetValue( 0 );
+ m_flYaw = 0;
+}
+
+void CRotationSlider::SetControlledObject( C_BaseObject *pObject )
+{
+ m_hObject.Set( pObject );
+}
+
+//-----------------------------------------------------------------------------
+// When the slider is activated, deactivated, or moves
+//-----------------------------------------------------------------------------
+void CRotationSlider::OnMousePressed( vgui::MouseCode code )
+{
+ BaseClass::OnMousePressed( code );
+
+ if (code != MOUSE_LEFT)
+ return;
+
+ C_BaseObject *pObj = m_hObject.Get();
+ if (pObj)
+ {
+ m_flInitialYaw = pObj->GetAbsAngles().y;
+ pObj->PreviewYaw( m_flInitialYaw );
+ pObj->ActivateYawPreview( true );
+ }
+}
+
+void CRotationSlider::OnSliderMoved( int position )
+{
+ C_BaseObject *pObj = m_hObject.Get();
+ if (pObj && pObj->IsPreviewingYaw())
+ {
+ m_flYaw = anglemod(position);
+ pObj->PreviewYaw( m_flInitialYaw - m_flYaw );
+ }
+}
+
+void CRotationSlider::OnMouseReleased( vgui::MouseCode code )
+{
+ BaseClass::OnMouseReleased( code );
+
+ if (code != MOUSE_LEFT)
+ return;
+
+ C_BaseObject *pObj = m_hObject.Get();
+ if (pObj)
+ {
+ char szbuf[48];
+ Q_snprintf( szbuf, sizeof( szbuf ), "yaw %0.2f\n", m_flInitialYaw - m_flYaw );
+ pObj->SendClientCommand( szbuf );
+ pObj->ActivateYawPreview( false );
+ SetValue(0);
+ m_flYaw = 0;
+ }
+}
diff --git a/game/client/tf/vgui/vgui_rotation_slider.h b/game/client/tf/vgui/vgui_rotation_slider.h
new file mode 100644
index 0000000..549068a
--- /dev/null
+++ b/game/client/tf/vgui/vgui_rotation_slider.h
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef VGUI_ROTATION_SLIDER_H
+#define VGUI_ROTATION_SLIDER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include <vgui_controls/Slider.h>
+#include "c_baseobject.h"
+
+
+//-----------------------------------------------------------------------------
+//
+// Slider for object control
+//
+//-----------------------------------------------------------------------------
+class CRotationSlider : public vgui::Slider
+{
+ DECLARE_CLASS_SIMPLE( CRotationSlider, vgui::Slider );
+
+public:
+ CRotationSlider( vgui::Panel *pParent, const char *pName );
+ void SetControlledObject( C_BaseObject *pObject );
+
+ virtual void OnMousePressed( vgui::MouseCode code );
+ virtual void OnMouseReleased( vgui::MouseCode code );
+ MESSAGE_FUNC_INT( OnSliderMoved, "SliderMoved", position );
+
+private:
+ CHandle<C_BaseObject> m_hObject;
+ float m_flInitialYaw;
+ float m_flYaw;
+};
+
+
+#endif // VGUI_ROTATION_SLIDER_H