Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "quest_notification_panel.h"
  8. #include "vgui/ISurface.h"
  9. #include "ienginevgui.h"
  10. #include "hudelement.h"
  11. #include "iclientmode.h"
  12. #include "basemodel_panel.h"
  13. #include "tf_item_inventory.h"
  14. #include "quest_log_panel.h"
  15. #include "econ_controls.h"
  16. #include "c_tf_player.h"
  17. #include <vgui_controls/AnimationController.h>
  18. #include "engine/IEngineSound.h"
  19. #include "econ_item_system.h"
  20. #include "tf_hud_item_progress_tracker.h"
  21. #include "tf_spectatorgui.h"
  22. #include "econ_quests.h"
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include <tier0/memdbgon.h>
  25. ConVar tf_quest_notification_line_delay( "tf_quest_notification_line_delay", "1.2", FCVAR_ARCHIVE );
  26. extern ISoundEmitterSystemBase *soundemitterbase;
  27. CQuestNotificationPanel *g_pQuestNotificationPanel = NULL;
  28. DECLARE_HUDELEMENT( CQuestNotificationPanel );
  29. CQuestNotification::CQuestNotification( CEconItem *pItem )
  30. : m_hItem( pItem )
  31. {}
  32. //-----------------------------------------------------------------------------
  33. // Purpose:
  34. //-----------------------------------------------------------------------------
  35. float CQuestNotification::Present( CQuestNotificationPanel* pNotificationPanel )
  36. {
  37. m_timerDialog.Start( tf_quest_notification_line_delay.GetFloat() );
  38. return 0.f;
  39. }
  40. //-----------------------------------------------------------------------------
  41. // Purpose:
  42. //-----------------------------------------------------------------------------
  43. CQuestNotification_Speaking::CQuestNotification_Speaking( CEconItem *pItem )
  44. : CQuestNotification( pItem )
  45. {
  46. m_pszSoundToSpeak = NULL;
  47. }
  48. //-----------------------------------------------------------------------------
  49. // Purpose:
  50. //-----------------------------------------------------------------------------
  51. float CQuestNotification_Speaking::Present( CQuestNotificationPanel* pNotificationPanel )
  52. {
  53. CQuestNotification::Present( pNotificationPanel );
  54. if ( m_hItem )
  55. {
  56. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  57. if ( !pPlayer )
  58. return 0.f;
  59. CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer );
  60. if ( !pTFPlayer )
  61. return 0.f;
  62. const GameItemDefinition_t *pItemDef = m_hItem->GetItemDefinition();
  63. // Get our quest theme
  64. const CQuestThemeDefinition *pTheme = pItemDef->GetQuestDef()->GetQuestTheme();
  65. if ( pTheme )
  66. {
  67. // Get the sound we need to speak
  68. m_pszSoundToSpeak = GetSoundEntry( pTheme, pTFPlayer->GetPlayerClass()->GetClassIndex() );
  69. float flPresentTime = 0.f;
  70. if ( m_pszSoundToSpeak )
  71. {
  72. flPresentTime = enginesound->GetSoundDuration( m_pszSoundToSpeak ) + m_timerDialog.GetCountdownDuration() + 1.f;
  73. m_timerShow.Start( enginesound->GetSoundDuration( m_pszSoundToSpeak ) + m_timerDialog.GetCountdownDuration() + 1.f );
  74. }
  75. return flPresentTime;
  76. }
  77. }
  78. return 0.f;
  79. }
  80. //-----------------------------------------------------------------------------
  81. // Purpose:
  82. //-----------------------------------------------------------------------------
  83. void CQuestNotification_Speaking::Update( CQuestNotificationPanel* pNotificationPanel )
  84. {
  85. if ( m_timerDialog.IsElapsed() && m_timerDialog.HasStarted() && m_hItem )
  86. {
  87. m_timerDialog.Invalidate();
  88. // Play it!
  89. if ( m_pszSoundToSpeak )
  90. {
  91. vgui::surface()->PlaySound( m_pszSoundToSpeak );
  92. }
  93. }
  94. }
  95. //-----------------------------------------------------------------------------
  96. // Purpose:
  97. //-----------------------------------------------------------------------------
  98. bool CQuestNotification_Speaking::IsDone() const
  99. {
  100. return m_timerShow.IsElapsed() && m_timerShow.HasStarted();
  101. }
  102. //-----------------------------------------------------------------------------
  103. // Purpose:
  104. //-----------------------------------------------------------------------------
  105. const char *CQuestNotification_NewQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex )
  106. {
  107. return pTheme->GetGiveSoundForClass( nClassIndex );
  108. }
  109. bool CQuestNotification_NewQuest::ShouldPresent() const
  110. {
  111. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  112. if ( !pPlayer )
  113. return false;
  114. CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer );
  115. if ( !pTFPlayer )
  116. return false;
  117. IViewPortPanel* pSpecGuiPanel = gViewPortInterface->FindPanelByName( PANEL_SPECGUI );
  118. if ( !pTFPlayer->IsAlive() )
  119. {
  120. if ( !pSpecGuiPanel || !pSpecGuiPanel->IsVisible() )
  121. return false;
  122. }
  123. else
  124. {
  125. // Local player is in a spawn room
  126. if ( pTFPlayer->m_Shared.GetRespawnTouchCount() <= 0 )
  127. return false;
  128. }
  129. return true;
  130. }
  131. CQuestNotification_CompletedQuest::CQuestNotification_CompletedQuest( CEconItem *pItem )
  132. : CQuestNotification_Speaking( pItem )
  133. {
  134. const char *pszSoundName = UTIL_GetRandomSoundFromEntry( "Quest.StatusTickComplete" );
  135. m_PresentTimer.Start( enginesound->GetSoundDuration( pszSoundName ) - 2.f );
  136. }
  137. //-----------------------------------------------------------------------------
  138. // Purpose:
  139. //-----------------------------------------------------------------------------
  140. const char *CQuestNotification_CompletedQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex )
  141. {
  142. return pTheme->GetCompleteSoundForClass( nClassIndex );
  143. }
  144. //-----------------------------------------------------------------------------
  145. // Purpose:
  146. //-----------------------------------------------------------------------------
  147. bool CQuestNotification_CompletedQuest::ShouldPresent() const
  148. {
  149. return m_PresentTimer.IsElapsed() && m_PresentTimer.HasStarted();
  150. }
  151. //-----------------------------------------------------------------------------
  152. // Purpose:
  153. //-----------------------------------------------------------------------------
  154. const char *CQuestNotification_FullyCompletedQuest::GetSoundEntry( const CQuestThemeDefinition* pTheme, int nClassIndex )
  155. {
  156. return pTheme->GetFullyCompleteSoundForClass( nClassIndex );
  157. }
  158. //-----------------------------------------------------------------------------
  159. // Purpose:
  160. //-----------------------------------------------------------------------------
  161. CQuestNotificationPanel::CQuestNotificationPanel( const char *pszElementName )
  162. : CHudElement( pszElementName )
  163. , EditablePanel( NULL, "QuestNotificationPanel" )
  164. , m_flTimeSinceLastShown( 0.f )
  165. , m_bIsPresenting( false )
  166. , m_mapNotifiedItemIDs( DefLessFunc( itemid_t ) )
  167. , m_bInitialized( false )
  168. , m_pMainContainer( NULL )
  169. {
  170. Panel *pParent = g_pClientMode->GetViewport();
  171. SetParent( pParent );
  172. g_pQuestNotificationPanel = this;
  173. ListenForGameEvent( "player_death" );
  174. ListenForGameEvent( "inventory_updated" );
  175. ListenForGameEvent( "player_initial_spawn" );
  176. }
  177. //-----------------------------------------------------------------------------
  178. // Purpose:
  179. //-----------------------------------------------------------------------------
  180. CQuestNotificationPanel::~CQuestNotificationPanel()
  181. {}
  182. void CQuestNotificationPanel::ApplySchemeSettings( IScheme *pScheme )
  183. {
  184. BaseClass::ApplySchemeSettings( pScheme );
  185. // Default, load pauling
  186. LoadControlSettings( "Resource/UI/econ/QuestNotificationPanel_Pauling_standard.res" );
  187. m_pMainContainer = FindControl< EditablePanel >( "MainContainer", true );
  188. }
  189. //-----------------------------------------------------------------------------
  190. // Purpose:
  191. //-----------------------------------------------------------------------------
  192. void CQuestNotificationPanel::PerformLayout()
  193. {
  194. BaseClass::PerformLayout();
  195. CExLabel* pNewQuestLabel = FindControl< CExLabel >( "NewQuestText", true );
  196. if ( pNewQuestLabel )
  197. {
  198. const wchar_t *pszText = NULL;
  199. const char *pszTextKey = "#QuestNotification_Accept";
  200. if ( pszTextKey )
  201. {
  202. pszText = g_pVGuiLocalize->Find( pszTextKey );
  203. }
  204. if ( pszText )
  205. {
  206. wchar_t wzFinal[512] = L"";
  207. UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) );
  208. pNewQuestLabel->SetText( wzFinal );
  209. }
  210. }
  211. }
  212. //-----------------------------------------------------------------------------
  213. // Purpose:
  214. //-----------------------------------------------------------------------------
  215. void CQuestNotificationPanel::FireGameEvent( IGameEvent * event )
  216. {
  217. const char *pszName = event->GetName();
  218. if ( FStrEq( pszName, "inventory_updated" ) || FStrEq( pszName, "player_death" ) )
  219. {
  220. CheckForNotificationOpportunities();
  221. }
  222. else if ( FStrEq( pszName, "player_initial_spawn" ) )
  223. {
  224. CTFPlayer *pNewPlayer = ToTFPlayer( UTIL_PlayerByIndex( event->GetInt( "index" ) ) );
  225. if ( pNewPlayer == C_BasePlayer::GetLocalPlayer() )
  226. {
  227. // Reset every round
  228. m_mapNotifiedItemIDs.Purge();
  229. m_vecNotifications.PurgeAndDeleteElements();
  230. m_timerNotificationCooldown.Start( 0 );
  231. m_bInitialized = false;
  232. }
  233. }
  234. }
  235. //-----------------------------------------------------------------------------
  236. // Purpose:
  237. //-----------------------------------------------------------------------------
  238. void CQuestNotificationPanel::Reset()
  239. {
  240. CheckForNotificationOpportunities();
  241. }
  242. //-----------------------------------------------------------------------------
  243. // Purpose:
  244. //-----------------------------------------------------------------------------
  245. void CQuestNotificationPanel::CheckForNotificationOpportunities()
  246. {
  247. // Suppress making new notifications while in competitive play
  248. if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
  249. return;
  250. FOR_EACH_VEC_BACK( m_vecNotifications, i )
  251. {
  252. // Clean up old entires for items that are now gone
  253. if ( m_vecNotifications[i]->GetItemHandle() == NULL )
  254. {
  255. delete m_vecNotifications[i];
  256. m_vecNotifications.Remove( i );
  257. }
  258. }
  259. CPlayerInventory *pInv = InventoryManager()->GetLocalInventory();
  260. Assert( pInv );
  261. if ( pInv )
  262. {
  263. for ( int i = 0 ; i < pInv->GetItemCount(); ++i )
  264. {
  265. CEconItemView *pItem = pInv->GetItem( i );
  266. // Check if this is a quest at all
  267. if ( pItem->GetItemDefinition()->GetQuestDef() == NULL )
  268. continue;
  269. CQuestNotification* pNotification = NULL;
  270. if ( IsUnacknowledged( pItem->GetInventoryPosition() ) )
  271. {
  272. pNotification = new CQuestNotification_NewQuest( pItem->GetSOCData() );
  273. }
  274. else if ( IsQuestItemFullyCompleted( pItem ) ) // Fully completed
  275. {
  276. pNotification = new CQuestNotification_FullyCompletedQuest( pItem->GetSOCData() );
  277. }
  278. else if ( IsQuestItemReadyToTurnIn( pItem ) ) // Ready to turn in
  279. {
  280. pNotification = new CQuestNotification_CompletedQuest( pItem->GetSOCData() );
  281. }
  282. else
  283. {
  284. // Clean up any pending notifications for normal quests
  285. FOR_EACH_VEC_BACK( m_vecNotifications, j )
  286. {
  287. if ( m_vecNotifications[j]->GetItemHandle() == pItem->GetSOCData() )
  288. {
  289. delete m_vecNotifications[j];
  290. m_vecNotifications.Remove( j );
  291. }
  292. }
  293. }
  294. if ( pNotification && !AddNotificationForItem( pItem, pNotification ) )
  295. {
  296. delete pNotification;
  297. pNotification = NULL;
  298. }
  299. }
  300. m_bInitialized = pInv->GetOwner().IsValid();
  301. }
  302. }
  303. //-----------------------------------------------------------------------------
  304. // Purpose:
  305. //-----------------------------------------------------------------------------
  306. bool CQuestNotificationPanel::AddNotificationForItem( const CEconItemView *pItem, CQuestNotification* pNotification )
  307. {
  308. bool bTypeAlreadyInQueue = false;
  309. // Check if there's already a notification of this type
  310. FOR_EACH_VEC_BACK( m_vecNotifications, i )
  311. {
  312. // There's already a quest of this type in queue, no need to add another
  313. if ( m_vecNotifications[i]->GetType() == pNotification->GetType() )
  314. {
  315. bTypeAlreadyInQueue = true;
  316. break;
  317. }
  318. }
  319. // Find the notified bits
  320. auto idx = m_mapNotifiedItemIDs.Find( pItem->GetItemID() );
  321. if ( idx == m_mapNotifiedItemIDs.InvalidIndex() )
  322. {
  323. // Create if missing
  324. idx = m_mapNotifiedItemIDs.Insert( pItem->GetItemID() );
  325. m_mapNotifiedItemIDs[ idx ].SetSize( CQuestNotification::NUM_NOTIFICATION_TYPES );
  326. FOR_EACH_VEC( m_mapNotifiedItemIDs[ idx ], i )
  327. {
  328. m_mapNotifiedItemIDs[ idx ][ i ] = 0.f;
  329. }
  330. }
  331. // Check if we've already done a notification for this type recently
  332. if ( Plat_FloatTime() < m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] || m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] == NEVER_REPEAT )
  333. {
  334. return false;
  335. }
  336. bool bNotificationUsed = false;
  337. // Don't play completed notifications unless they happen mid-play
  338. if ( !bTypeAlreadyInQueue && ( m_bInitialized || pNotification->GetType() == CQuestNotification::NOTIFICATION_TYPE_NEW_QUEST ) )
  339. {
  340. // Add notification
  341. m_vecNotifications.AddToTail( pNotification );
  342. bNotificationUsed = true;
  343. }
  344. // Mark that we've created a notification of this type for this item
  345. m_mapNotifiedItemIDs[ idx ][ pNotification->GetType() ] = pNotification->GetReplayTime() == NEVER_REPEAT ? NEVER_REPEAT : Plat_FloatTime() + pNotification->GetReplayTime();
  346. return bNotificationUsed;
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose:
  350. //-----------------------------------------------------------------------------
  351. bool CQuestNotificationPanel::ShouldDraw()
  352. {
  353. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  354. if ( !pPlayer )
  355. return false;
  356. CTFPlayer* pTFPlayer = ToTFPlayer( pPlayer );
  357. if ( !pTFPlayer )
  358. return false;
  359. // Not selected a class, so they haven't joined in
  360. if ( pTFPlayer->IsPlayerClass( 0 ) )
  361. return false;
  362. if ( !CHudElement::ShouldDraw() )
  363. return false;
  364. if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
  365. return false;
  366. return true;
  367. }
  368. //-----------------------------------------------------------------------------
  369. // Purpose:
  370. //-----------------------------------------------------------------------------
  371. void CQuestNotificationPanel::OnThink()
  372. {
  373. if ( !ShouldDraw() )
  374. return;
  375. bool bHasStarted = m_animTimer.HasStarted();
  376. float flShowProgress = bHasStarted ? 1.f : 0.f;
  377. const float flTransitionTime = 0.5f;
  378. Update();
  379. if ( bHasStarted )
  380. {
  381. // Transitions
  382. if ( m_animTimer.GetElapsedTime() < flTransitionTime )
  383. {
  384. flShowProgress = Bias( m_animTimer.GetElapsedTime() / flTransitionTime, 0.75f );
  385. }
  386. else if ( ( m_animTimer.GetRemainingTime() + 1.f ) < flTransitionTime )
  387. {
  388. flShowProgress = Bias( Max( 0.0f, m_animTimer.GetRemainingTime() + 1.f ) / flTransitionTime, 0.25f );
  389. }
  390. }
  391. // Move the main container around
  392. if ( m_pMainContainer )
  393. {
  394. int nY = g_pSpectatorGUI && g_pSpectatorGUI->IsVisible() ? g_pSpectatorGUI->GetTopBarHeight() : 0;
  395. float flXPos = RemapValClamped( flShowProgress, 0.f, 1.f, 0.f, m_pMainContainer->GetWide() + XRES( 4 ) );
  396. m_pMainContainer->SetPos( GetWide() - (int)flXPos, nY );
  397. }
  398. }
  399. //-----------------------------------------------------------------------------
  400. // Purpose:
  401. //-----------------------------------------------------------------------------
  402. bool CQuestNotificationPanel::ShouldPresent()
  403. {
  404. if ( !m_timerNotificationCooldown.IsElapsed() )
  405. return false;
  406. // We need notifications!
  407. if ( m_vecNotifications.IsEmpty() )
  408. return false;
  409. // It's been a few seconds since we were last shown
  410. if ( ( Plat_FloatTime() - m_flTimeSinceLastShown ) < 1.5f )
  411. return false;
  412. return true;
  413. }
  414. //-----------------------------------------------------------------------------
  415. // Purpose:
  416. //-----------------------------------------------------------------------------
  417. void CQuestNotificationPanel::Update()
  418. {
  419. bool bAllowedToShow = ShouldPresent();
  420. if ( bAllowedToShow && !m_bIsPresenting )
  421. {
  422. if ( m_vecNotifications.Head()->ShouldPresent() )
  423. {
  424. float flPresentTime = m_vecNotifications.Head()->Present( this );
  425. m_animTimer.Start( flPresentTime );
  426. m_timerHoldUp.Start( 3.f );
  427. // Notification sound
  428. vgui::surface()->PlaySound( "ui/quest_alert.wav" );
  429. m_bIsPresenting = true;
  430. }
  431. }
  432. else if ( !bAllowedToShow && m_bIsPresenting && m_timerHoldUp.IsElapsed() )
  433. {
  434. m_flTimeSinceLastShown = Plat_FloatTime();
  435. // Play the slide-out animation
  436. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "QuestNotification_Hide" );
  437. m_bIsPresenting = false;
  438. }
  439. else if ( m_bIsPresenting ) // We are presenting a notification
  440. {
  441. if ( m_vecNotifications.Count() )
  442. {
  443. m_vecNotifications.Head()->Update( this );
  444. // Check if the notification is done
  445. if ( m_vecNotifications.Head()->IsDone() )
  446. {
  447. // Start our cooldown
  448. m_timerNotificationCooldown.Start( 1.f );
  449. // We're done with this notification
  450. delete m_vecNotifications.Head();
  451. m_vecNotifications.Remove( 0 );
  452. }
  453. }
  454. }
  455. }