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.
1670 lines
48 KiB
1670 lines
48 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "c_func_passtime_goal.h" |
|
#include "c_tf_passtime_ball.h" |
|
#include "c_tf_passtime_logic.h" |
|
#include "tf_hud_passtime.h" |
|
#include "tf_hud_passtime_ball_offscreen_arrow.h" |
|
#include "tf_weapon_passtime_gun.h" |
|
#include "passtime_convars.h" |
|
#include "passtime_game_events.h" |
|
#include "tf_hud_freezepanel.h" |
|
#include "tf_gamerules.h" |
|
#include "c_tf_team.h" |
|
#include "c_tf_player.h" |
|
#include "c_tf_playerresource.h" |
|
#include "iclientmode.h" |
|
#include "vgui_controls/AnimationController.h" |
|
#include "vgui_controls/CircularProgressBar.h" |
|
#include "vgui_controls/ProgressBar.h" |
|
#include "vgui/ISurface.h" |
|
#include <algorithm> |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
using namespace vgui; |
|
|
|
//----------------------------------------------------------------------------- |
|
// The team colors from g_PR are wrong for probably good reasons that I don't understand. |
|
static Color GetTeamColor( int iTeam, byte alpha = 255 ) |
|
{ |
|
switch ( iTeam ) |
|
{ |
|
case TF_TEAM_RED: return Color( 159, 55, 34, alpha ); |
|
case TF_TEAM_BLUE: return Color( 76, 109, 128, alpha ); |
|
default: return Color( 245, 231, 222, alpha ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
static const char *GetProgressBallImageForTeam( int iTeam ) |
|
{ |
|
switch( iTeam ) |
|
{ |
|
case TF_TEAM_RED: return "../passtime/hud/passtime_ballcontrol_red"; |
|
case TF_TEAM_BLUE: return "../passtime/hud/passtime_ballcontrol_blue"; |
|
default: return "../passtime/hud/passtime_ballcontrol_none"; |
|
}; |
|
} |
|
static const char *GetProgressBallImageForTeam( C_BaseEntity *pEnt ) |
|
{ |
|
if ( !pEnt ) |
|
{ |
|
return "../passtime/hud/passtime_ball"; |
|
} |
|
return GetProgressBallImageForTeam( pEnt->GetTeamNumber() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
static const char *GetPlayerProgressPortrait( C_TFPlayer *pPlayer ) |
|
{ |
|
if ( !pPlayer ) |
|
{ |
|
return "../passtime/hud/portrait_scout_red"; |
|
} |
|
|
|
int iTeam = pPlayer->GetTeamNumber(); |
|
int iClass = pPlayer->GetPlayerClass()->GetClassIndex(); |
|
|
|
switch(iClass) |
|
{ |
|
case TF_CLASS_SOLDIER: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_soldier_red" : "../passtime/hud/portrait_soldier_blu"; |
|
case TF_CLASS_SCOUT: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_scout_red" : "../passtime/hud/portrait_scout_blu"; |
|
case TF_CLASS_SNIPER: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_sniper_red" : "../passtime/hud/portrait_sniper_blu"; |
|
case TF_CLASS_DEMOMAN: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_demo_red" : "../passtime/hud/portrait_demo_blu"; |
|
case TF_CLASS_MEDIC: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_medic_red" : "../passtime/hud/portrait_medic_blu"; |
|
case TF_CLASS_HEAVYWEAPONS: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_heavy_red" : "../passtime/hud/portrait_heavy_blu"; |
|
case TF_CLASS_PYRO: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_pyro_red" : "../passtime/hud/portrait_pyro_blu"; |
|
case TF_CLASS_SPY: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_spy_red" : "../passtime/hud/portrait_spy_blu"; |
|
case TF_CLASS_ENGINEER: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_eng_red" : "../passtime/hud/portrait_eng_blu"; |
|
default: |
|
return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_scout_red" : "../passtime/hud/portrait_scout_blu"; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
static const char *GetProgressGoalImage( const C_FuncPasstimeGoal *pGoal ) |
|
{ |
|
if ( !pGoal ) |
|
{ |
|
return ""; |
|
} |
|
|
|
// NOTE: keep in mind that a goal that's on team blue is the goal where blue scores |
|
// and should look red on the hud since it's in the red part of the map. oops. |
|
bool bRedIcon = pGoal->GetTeamNumber() == TF_TEAM_BLUE; |
|
|
|
if ( pGoal->GetGoalType() == C_FuncPasstimeGoal::TYPE_TOWER ) |
|
{ |
|
if ( pGoal->BGoalTriggerDisabled() ) |
|
{ |
|
return bRedIcon |
|
? "../passtime/hud/passtime_goal_red_locked" |
|
: "../passtime/hud/passtime_goal_blue_locked"; |
|
} |
|
else |
|
{ |
|
return bRedIcon |
|
? "../passtime/hud/passtime_goal_red_unlocked" |
|
: "../passtime/hud/passtime_goal_blue_unlocked"; |
|
} |
|
} |
|
else if ( pGoal->GetGoalType() == C_FuncPasstimeGoal::TYPE_ENDZONE ) |
|
{ |
|
return bRedIcon |
|
? "../passtime/hud/passtime_endzone_red_icon" |
|
: "../passtime/hud/passtime_endzone_blue_icon"; |
|
} |
|
else |
|
{ |
|
return bRedIcon |
|
? "../passtime/hud/passtime_goal_red_icon" |
|
: "../passtime/hud/passtime_goal_blue_icon"; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CTFHudPasstimePanel |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstimePanel::CTFHudPasstimePanel( vgui::Panel *pParent, const char* name ) |
|
: EditablePanel( pParent, name ) |
|
{} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFHudPasstimePanel::IsVisible() |
|
{ |
|
if ( IsTakingAFreezecamScreenshot() ) |
|
{ |
|
return false; |
|
} |
|
return BaseClass::IsVisible(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// CTFHudTeamScore |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudTeamScore::CTFHudTeamScore( vgui::Panel *pParent ) |
|
: CTFHudPasstimePanel( pParent, "HudTeamScore" ) |
|
, m_pPlayingToCluster( 0 ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudTeamScore::ApplySchemeSettings( vgui::IScheme *pScheme ) |
|
{ |
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
LoadControlSettings( "resource/UI/HudPasstimeTeamScore.res", NULL, NULL, NULL ); |
|
m_pPlayingToCluster = FindControl<EditablePanel>( "PlayingToCluster" ); |
|
|
|
if ( m_pPlayingToCluster ) |
|
{ |
|
m_pPlayingToCluster->SetVisible( true ); |
|
} |
|
|
|
OnTick(); |
|
vgui::ivgui()->AddTickSignal( GetVPanel() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudTeamScore::OnTick() |
|
{ |
|
// I would rather not do this every tick, but i couldn't find a reliable way |
|
// to do it from events. |
|
if( !g_pPasstimeLogic ) |
|
{ |
|
return; |
|
} |
|
|
|
int iBlueScore = 0; |
|
int iRedScore = 0; |
|
|
|
C_TFTeam *pTeam = GetGlobalTFTeam( TF_TEAM_BLUE ); |
|
if ( pTeam ) |
|
{ |
|
iBlueScore = pTeam->GetFlagCaptures(); |
|
} |
|
|
|
pTeam = GetGlobalTFTeam( TF_TEAM_RED ); |
|
if ( pTeam ) |
|
{ |
|
iRedScore = pTeam->GetFlagCaptures(); |
|
} |
|
|
|
if ( m_pPlayingToCluster ) |
|
{ |
|
m_pPlayingToCluster->SetDialogVariable( "rounds", |
|
tf_passtime_scores_per_round.GetInt() ); |
|
} |
|
|
|
SetDialogVariable( "bluescore", iBlueScore ); |
|
SetDialogVariable( "redscore", iRedScore ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int CTFHudTeamScore::GetTeamScore( int iTeam ) |
|
{ |
|
C_TFTeam *pTeam = GetGlobalTFTeam( iTeam ); |
|
return pTeam |
|
? pTeam->Get_Score() |
|
: 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CTFHudPasstimePassNotify |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstimePassNotify::CTFHudPasstimePassNotify( vgui::Panel *pParent ) |
|
: CTFHudPasstimePanel( pParent, "HudPasstimePassNotify" ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimePassNotify::ApplySchemeSettings( vgui::IScheme *pScheme ) |
|
{ |
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
LoadControlSettings( "resource/UI/HudPasstimePassNotify.res", NULL, NULL, NULL ); |
|
|
|
m_pTextBox = FindControl<EditablePanel>( "TextBox" ); |
|
m_pTextInPassRange = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextInPassRange" ) : NULL; |
|
m_pTextLockedOn = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextLockedOn" ) : NULL; |
|
m_pTextPassIncoming = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextPassIncoming" ) : NULL; |
|
m_pTextPlayerName = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextPlayerName" ) : NULL; |
|
m_pSpeechIndicator = FindControl<ImagePanel>( "SpeechIndicator" ); |
|
m_pPassLockIndicator = FindControl<ImagePanel>( "PassLockIndicator" ); |
|
m_pTextBoxBorderNormal = pScheme->GetBorder( "TFFatLineBorder" ); |
|
m_pTextBoxBorderIncomingRed = pScheme->GetBorder( "TFFatLineBorderRedBG" ); |
|
m_pTextBoxBorderIncomingBlu = pScheme->GetBorder( "TFFatLineBorderBlueBG" ); |
|
vgui::ivgui()->AddTickSignal( GetVPanel() ); |
|
|
|
Assert( m_pTextInPassRange && m_pTextLockedOn && m_pTextPassIncoming && m_pTextPlayerName |
|
&& m_pSpeechIndicator && m_pPassLockIndicator ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimePassNotify::OnTick() |
|
{ |
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
if ( !pLocalPlayer || !g_pPasstimeLogic || !g_pPasstimeLogic->GetBall() ) |
|
{ |
|
// nothing can work |
|
if ( m_pTextBox ) |
|
{ |
|
m_pTextBox->SetVisible( false ); |
|
} |
|
|
|
if ( m_pSpeechIndicator ) |
|
{ |
|
m_pSpeechIndicator->SetVisible( false ); |
|
} |
|
|
|
if ( m_pPassLockIndicator ) |
|
{ |
|
m_pPassLockIndicator->SetVisible( false ); |
|
} |
|
return; |
|
} |
|
|
|
C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall(); |
|
CTFPlayer *pBallCarrier = pBall ? pBall->GetCarrier() : NULL; |
|
if ( !pBallCarrier ) |
|
{ |
|
if ( pBall && pBall->GetHomingTarget() == pLocalPlayer ) |
|
{ |
|
// |
|
// Incoming pass |
|
// |
|
if ( m_pSpeechIndicator ) |
|
{ |
|
m_pSpeechIndicator->SetVisible( false ); |
|
} |
|
|
|
if ( m_pPassLockIndicator ) |
|
{ |
|
m_pPassLockIndicator->SetVisible( false ); |
|
} |
|
|
|
if ( m_pTextBox ) |
|
{ |
|
m_pTextBox->SetVisible( true ); |
|
} |
|
|
|
// this should really be GetThrower instead of PrevCarrier, |
|
// but it doesn't exist on the client and this will have the |
|
// desired value anyway. |
|
C_TFPlayer *pThrower = pBall->GetPrevCarrier(); |
|
if ( pThrower ) |
|
{ |
|
if ( m_pTextPlayerName ) |
|
{ |
|
m_pTextPlayerName->SetText( pThrower->GetPlayerName() ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_pTextPlayerName ) |
|
{ |
|
m_pTextPlayerName->SetText( "..." ); |
|
} |
|
} |
|
|
|
if ( m_pTextLockedOn ) |
|
{ |
|
m_pTextLockedOn->SetVisible( false ); |
|
} |
|
|
|
if ( m_pTextPassIncoming ) |
|
{ |
|
m_pTextPassIncoming->SetVisible( true ); |
|
} |
|
|
|
if ( m_pTextInPassRange ) |
|
{ |
|
m_pTextInPassRange->SetVisible( false ); |
|
} |
|
|
|
if ( m_pTextBox ) |
|
{ |
|
m_pTextBox->SetBorder( (pLocalPlayer->GetTeamNumber() == TF_TEAM_RED) |
|
? m_pTextBoxBorderIncomingRed |
|
: m_pTextBoxBorderIncomingBlu ); |
|
} |
|
|
|
return; |
|
} |
|
} |
|
|
|
// |
|
// Can't be an incoming pass at this point |
|
// |
|
|
|
if( !pBallCarrier |
|
|| pLocalPlayer->IsObserver() |
|
|| (pBallCarrier == pLocalPlayer) |
|
|| (pBallCarrier->GetTeamNumber() != pLocalPlayer->GetTeamNumber()) ) |
|
{ |
|
// |
|
// No carrier, or carrier is on enemy team, or carrier is local player |
|
// |
|
|
|
if ( m_pTextBox ) |
|
{ |
|
m_pTextBox->SetVisible( false ); |
|
} |
|
|
|
if ( m_pSpeechIndicator ) |
|
{ |
|
m_pSpeechIndicator->SetVisible( false ); |
|
} |
|
|
|
if ( m_pPassLockIndicator ) |
|
{ |
|
m_pPassLockIndicator->SetVisible( false ); |
|
} |
|
return; |
|
} |
|
|
|
// |
|
// Carrier has the ball and is on our team |
|
// |
|
|
|
float flMaxPassRangeSqr = g_pPasstimeLogic->GetMaxPassRange(); |
|
flMaxPassRangeSqr *= flMaxPassRangeSqr; |
|
bool bTargetable = pBallCarrier->EyePosition().DistToSqr( pLocalPlayer->EyePosition() ) < flMaxPassRangeSqr; |
|
if ( bTargetable ) |
|
{ |
|
trace_t tr; |
|
CTraceFilterIgnorePlayers tracefilter( pLocalPlayer, COLLISION_GROUP_PROJECTILE ); |
|
UTIL_TraceLine( pBallCarrier->EyePosition(), pLocalPlayer->EyePosition(), MASK_PLAYERSOLID, &tracefilter, &tr ); |
|
bTargetable = tr.fraction == 1; |
|
} |
|
|
|
if ( !bTargetable ) |
|
{ |
|
if ( m_pTextBox ) |
|
{ |
|
m_pTextBox->SetVisible( false ); |
|
} |
|
|
|
if ( m_pSpeechIndicator ) |
|
{ |
|
m_pSpeechIndicator->SetVisible( false ); |
|
} |
|
|
|
if ( m_pPassLockIndicator ) |
|
{ |
|
m_pPassLockIndicator->SetVisible( false ); |
|
} |
|
return; |
|
} |
|
|
|
bool bTargeted = pLocalPlayer->m_Shared.IsTargetedForPasstimePass(); |
|
|
|
if ( m_pTextBox ) |
|
{ |
|
m_pTextBox->SetVisible( true ); |
|
} |
|
|
|
if ( m_pPassLockIndicator ) |
|
{ |
|
m_pPassLockIndicator->SetVisible( bTargeted ); |
|
} |
|
|
|
if ( m_pSpeechIndicator ) |
|
{ |
|
m_pSpeechIndicator->SetVisible( pLocalPlayer->m_Shared.AskForBallTime() > gpGlobals->curtime ); |
|
} |
|
|
|
if ( bTargeted ) |
|
{ |
|
if ( m_pPassLockIndicator ) |
|
{ |
|
m_pPassLockIndicator->SetDrawColor( GetTeamColor( pLocalPlayer->GetTeamNumber() ) ); |
|
} |
|
|
|
if ( m_pTextLockedOn ) |
|
{ |
|
m_pTextLockedOn->SetVisible( true ); |
|
} |
|
|
|
if ( m_pTextPassIncoming ) |
|
{ |
|
m_pTextPassIncoming->SetVisible( false ); |
|
} |
|
|
|
if ( m_pTextInPassRange ) |
|
{ |
|
m_pTextInPassRange->SetVisible( false ); |
|
} |
|
} |
|
else if ( bTargetable ) |
|
{ |
|
if ( m_pTextLockedOn ) |
|
{ |
|
m_pTextLockedOn->SetVisible( false ); |
|
} |
|
|
|
if ( m_pTextPassIncoming ) |
|
{ |
|
m_pTextPassIncoming->SetVisible( false ); |
|
} |
|
|
|
if ( m_pTextInPassRange ) |
|
{ |
|
m_pTextInPassRange->SetVisible( true ); |
|
} |
|
} |
|
//else if ( !bTargetable ) |
|
//{ |
|
// m_pTopText->SetText( "CAN'T SEE YOU" ); |
|
//} |
|
|
|
if ( m_pTextPlayerName ) |
|
{ |
|
m_pTextPlayerName->SetText( pBallCarrier->GetPlayerName() ); |
|
} |
|
|
|
if ( m_pTextBox ) |
|
{ |
|
m_pTextBox->SetBorder( m_pTextBoxBorderNormal ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CTFHudPasstimeEventText |
|
//----------------------------------------------------------------------------- |
|
|
|
namespace HudPasstimeEventText |
|
{ |
|
static const float flInSec = 0.1f; |
|
static const float flOutSec = 0.25f; |
|
static const float flShowSec = 3.0f; |
|
static const float flPauseSec = 0.1f; |
|
static const int iQueueDepthPanic = 3; |
|
static char const * const pKeyTeam = "team"; |
|
static char const * const pKeySubject = "subject"; |
|
static char const * const pKeySource = "source"; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstimeEventText::QueueElement::QueueElement() |
|
{ |
|
title[0] = (wchar_t)0; |
|
detail[0] = (wchar_t)0; |
|
bonus[0] = (wchar_t)0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstimeEventText::CTFHudPasstimeEventText() |
|
: m_localizeKeys( "" ) |
|
{ |
|
m_bValid = false; |
|
m_pTitleLabel = 0; |
|
m_pDetailLabel = 0; |
|
m_pBonusLabel = 0; |
|
m_state = State::Idle; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstimeEventText::~CTFHudPasstimeEventText() |
|
{ |
|
Clear(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// static |
|
void CTFHudPasstimeEventText::SetLabelText( vgui::Label *pLabel, const wchar_t *pText ) |
|
{ |
|
if ( pText && pText[0] ) |
|
{ |
|
pLabel->SetVisible( true ); |
|
pLabel->SetText( pText ); |
|
} |
|
else |
|
{ |
|
pLabel->SetVisible( false ); |
|
pLabel->SetText( L"" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::EnterState( State state, float duration ) |
|
{ |
|
m_state = state; |
|
|
|
// move things faster if the queue is backlogged, but State::Pause is always the same duration |
|
if ( (state != State::Pause) && (m_queue.Count() >= HudPasstimeEventText::iQueueDepthPanic) ) |
|
{ |
|
duration /= 2.0f; |
|
} |
|
m_displayTimer.Start( duration ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::SetAlpha( int ia ) |
|
{ |
|
if ( m_pTitleLabel ) |
|
{ |
|
m_pTitleLabel->SetAlpha( ia ); |
|
m_pTitleLabel->SetVisible( ia != 0 ); |
|
} |
|
|
|
if ( m_pDetailLabel ) |
|
{ |
|
m_pDetailLabel->SetAlpha( ia ); |
|
m_pDetailLabel->SetVisible( ia != 0 ); |
|
} |
|
|
|
if ( m_pBonusLabel ) |
|
{ |
|
m_pBonusLabel->SetAlpha( ia ); |
|
m_pBonusLabel->SetVisible( ia != 0 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::Tick() |
|
{ |
|
if ( !m_bValid ) |
|
return; |
|
|
|
switch( m_state ) |
|
{ |
|
case State::Idle: |
|
if ( !m_queue.IsEmpty() ) |
|
{ |
|
SetAlpha( 1 ); |
|
auto msg = m_queue.RemoveAtHead(); |
|
if ( m_pTitleLabel ) |
|
{ |
|
SetLabelText( m_pTitleLabel, msg.title ); |
|
} |
|
if ( m_pDetailLabel ) |
|
{ |
|
SetLabelText( m_pDetailLabel, msg.detail ); |
|
} |
|
if ( m_pBonusLabel ) |
|
{ |
|
SetLabelText( m_pBonusLabel, msg.bonus ); |
|
} |
|
EnterState( State::In, HudPasstimeEventText::flInSec ); |
|
} |
|
else |
|
{ |
|
SetAlpha( 0 ); |
|
} |
|
break; |
|
|
|
case State::In: |
|
if ( m_displayTimer.IsElapsed() ) |
|
{ |
|
EnterState( State::Show, HudPasstimeEventText::flShowSec ); |
|
SetAlpha( 255 ); |
|
} |
|
else |
|
{ |
|
// animate alpha |
|
// note: GetElapsedTime() |
|
float flFrac = m_displayTimer.GetElapsedTime() / m_displayTimer.GetCountdownDuration(); |
|
SetAlpha( (int) (flFrac * flFrac * 255.0f) ); |
|
} |
|
break; |
|
|
|
case State::Show: |
|
if ( m_displayTimer.IsElapsed() ) |
|
{ |
|
EnterState( State::Out, HudPasstimeEventText::flOutSec ); |
|
} |
|
break; |
|
|
|
case State::Out: |
|
if ( m_displayTimer.IsElapsed() ) |
|
{ |
|
EnterState( State::Pause, HudPasstimeEventText::flPauseSec ); |
|
SetAlpha( 0 ); |
|
} |
|
else |
|
{ |
|
// animate alpha |
|
// note: GetRemainingTime() |
|
float flFrac = m_displayTimer.GetRemainingTime() / m_displayTimer.GetCountdownDuration(); |
|
SetAlpha( (int) (flFrac * flFrac * 255.0f) ); |
|
} |
|
break; |
|
|
|
case State::Pause: |
|
if ( m_displayTimer.IsElapsed() ) |
|
{ |
|
m_state = State::Idle; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::Clear() |
|
{ |
|
while( !m_queue.IsEmpty() ) |
|
{ |
|
m_queue.RemoveAtTail(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::SetControls( vgui::Label *pTitleLabel, vgui::Label *pDetailLabel, vgui::Label *pBonusLabel ) |
|
{ |
|
m_pTitleLabel = pTitleLabel; |
|
m_pDetailLabel = pDetailLabel; |
|
m_pBonusLabel = pBonusLabel; |
|
m_bValid = pTitleLabel && pDetailLabel && pBonusLabel; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::SetPlayerName( C_TFPlayer *pPlayer, const char *pKey ) |
|
{ |
|
if ( pPlayer ) |
|
{ |
|
g_pVGuiLocalize->ConvertANSIToUnicode( pPlayer->GetPlayerName(), m_pwcsBuf, sizeof(m_pwcsBuf) ); |
|
m_localizeKeys->SetWString( pKey, m_pwcsBuf ); |
|
} |
|
else |
|
{ |
|
m_localizeKeys->SetWString( pKey, L"" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::SetTeam( C_TFPlayer *pPlayer ) |
|
{ |
|
if ( pPlayer ) |
|
{ |
|
C_TFTeam *pTeam = GetGlobalTFTeam( pPlayer->GetTeamNumber() ); |
|
m_localizeKeys->SetWString( "team", pTeam ? pTeam->Get_Localized_Name() : L"" ); |
|
} |
|
else |
|
{ |
|
m_localizeKeys->SetWString( "team", L"" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
template< int TArraySize > |
|
void CTFHudPasstimeEventText::ConstructNewString( const char *pLocTag, wchar_t (&out)[TArraySize] ) |
|
{ |
|
// FIXME calling find is redundant |
|
if ( pLocTag && pLocTag[0] && g_pVGuiLocalize->Find( pLocTag ) ) |
|
{ |
|
g_pVGuiLocalize->ConstructString_safe( out, pLocTag, m_localizeKeys ); |
|
} |
|
else |
|
{ |
|
out[0] = (wchar_t)0; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::Enqueue( C_TFPlayer *pSource, C_TFPlayer *pSubject, const char *pTitle, const char *pDetail, const char *pBonus ) |
|
{ |
|
if ( !m_bValid || !pSubject ) |
|
return; |
|
|
|
SetTeam( pSubject ); |
|
SetPlayerName( pSubject, HudPasstimeEventText::pKeySubject ); |
|
SetPlayerName( pSource, HudPasstimeEventText::pKeySource ); |
|
|
|
auto *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
auto bShowBonus = (pSubject == pLocalPlayer) |
|
|| (pLocalPlayer->IsObserver() && pLocalPlayer->GetObserverTarget() == pLocalPlayer); |
|
|
|
QueueElement e; |
|
ConstructNewString( pTitle, e.title ); |
|
ConstructNewString( pDetail, e.detail ); |
|
ConstructNewString( bShowBonus ? pBonus : nullptr, e.bonus ); |
|
m_queue.Insert( e ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::EnqueueGeneric( const char *pTitle, const char *pDetail, const char *pBonus ) |
|
{ |
|
QueueElement e; |
|
ConstructNewString( pTitle, e.title ); |
|
ConstructNewString( pDetail, e.detail ); |
|
ConstructNewString( pBonus, e.bonus ); |
|
m_queue.Insert( e ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::EnqueueSteal( C_TFPlayer *pVictim, C_TFPlayer *pStealer ) |
|
{ |
|
Enqueue( pVictim, pStealer, "#Msg_PasstimeEventStealTitle", "#Msg_PasstimeEventStealDetail", "#Msg_PasstimeEventStealBonus" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::EnqueuePass( C_TFPlayer *pThrower, C_TFPlayer *pCatcher ) |
|
{ |
|
Enqueue( pThrower, pCatcher, "#Msg_PasstimeEventPassTitle", "#Msg_PasstimeEventPassDetail", "#Msg_PasstimeEventPassBonus" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::EnqueueInterception( C_TFPlayer *pThrower, C_TFPlayer *pCatcher ) |
|
{ |
|
Enqueue( pThrower, pCatcher, "#Msg_PasstimeEventInterceptTitle", "#Msg_PasstimeEventInterceptDetail", "#Msg_PasstimeEventInterceptBonus" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeEventText::EnqueueScore( C_TFPlayer *pThrower, C_TFPlayer *pAssister ) |
|
{ |
|
if ( pAssister ) |
|
Enqueue( pAssister, pThrower, "#Msg_PasstimeEventScoreTitle", "#Msg_PasstimeEventScoreDetail_Assist", "#Msg_PasstimeEventScoreBonus" ); |
|
else |
|
Enqueue( pAssister, pThrower, "#Msg_PasstimeEventScoreTitle", "#Msg_PasstimeEventScoreDetail_NoAssist", "#Msg_PasstimeEventScoreBonus" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CTFHudPasstimeBallStatus |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstimeBallStatus::CTFHudPasstimeBallStatus( Panel *pParent ) |
|
: CTFHudPasstimePanel( pParent, "HudPasstimeBallStatus" ) |
|
{ |
|
m_bInitialized = false; |
|
m_bReset = false; |
|
m_bGoalsFound = false; |
|
m_iXBlueProgress = -100.0f; |
|
m_iXRedProgress = -100.0f; |
|
m_iYBlueProgress = -100.0f; |
|
m_iYRedProgress = -100.0f; |
|
memset( m_pGoalIconsBlue, 0, sizeof( m_pGoalIconsBlue ) ); |
|
memset( m_pGoalIconsRed, 0, sizeof( m_pGoalIconsRed ) ); |
|
memset( m_pPlayerIcons, 0, sizeof( m_pPlayerIcons ) ); |
|
m_pProgressBall = 0; |
|
m_pProgressBallCarrierName = 0; |
|
m_pProgressLevelBar = 0; |
|
m_pSelfPlayerIcon = 0; |
|
m_pEventText = new CTFHudPasstimeEventText(); |
|
m_pBallPowerMeterFillContainer = nullptr; |
|
m_pBallPowerMeterFill = nullptr; |
|
m_pBallPowerMeterFrame = nullptr; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstimeBallStatus::~CTFHudPasstimeBallStatus() |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::ApplySchemeSettings( IScheme *pScheme ) |
|
{ |
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
m_bInitialized = false; |
|
|
|
LoadControlSettings( "resource/UI/HudPasstimeBallStatus.res", NULL, NULL, NULL ); |
|
m_pPowerCluster = FindControl<EditablePanel>( "BallPowerCluster" ); |
|
m_pProgressBall = FindControl<ImagePanel>( "ProgressBallIcon" ); |
|
m_pProgressBallCarrierName = FindControl<Label>( "ProgressBallCarrierName" ); |
|
m_pProgressLevelBar = FindControl<Panel>( "ProgressLevelBar" ); |
|
m_pSelfPlayerIcon = FindControl<ImagePanel>( "ProgressSelfPlayerIcon" ); |
|
|
|
Panel *pBallPowerRoot = FindControl<Panel>( "BallPowerCluster" ); |
|
if ( !pBallPowerRoot ) |
|
{ |
|
pBallPowerRoot = this; |
|
} |
|
m_pBallPowerMeterFillContainer = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFillContainer", true ); |
|
m_pBallPowerMeterFill = pBallPowerRoot->FindControl<ImagePanel>( "BallPowerMeterFill", true ); |
|
m_pBallPowerMeterFrame = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFrame", true ); |
|
m_pBallPowerMeterFinalSection = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFinalSectionContainer", true ); |
|
|
|
m_pEventText->SetControls( FindControl<Label>( "EventTitleLabel" ), |
|
FindControl<Label>( "EventDetailLabel" ), |
|
FindControl<Label>( "EventBonusLabel" ) ); |
|
|
|
m_bInitialized = m_pProgressBall |
|
&& m_pProgressBallCarrierName |
|
&& m_pProgressLevelBar |
|
&& m_pSelfPlayerIcon |
|
&& m_pBallPowerMeterFillContainer |
|
&& m_pBallPowerMeterFill |
|
&& m_pBallPowerMeterFinalSection |
|
&& m_pBallPowerMeterFrame; |
|
|
|
if ( !m_bInitialized ) |
|
{ |
|
// just bail if the res file is missing required controls |
|
// this prevents a lot of stupid null checks in the future |
|
return; |
|
} |
|
|
|
// set up some ballpower stuff |
|
m_iBallPowerMeterFillWidth = m_pBallPowerMeterFillContainer ? m_pBallPowerMeterFillContainer->GetWide() : 0; |
|
int iFinalSectionWidth = (int)(m_iBallPowerMeterFillWidth * 0.2f); |
|
if ( m_pBallPowerMeterFinalSection ) |
|
{ |
|
m_pBallPowerMeterFinalSection->SetWide( iFinalSectionWidth ); |
|
m_pBallPowerMeterFinalSection->SetPos( m_pBallPowerMeterFinalSection->GetXPos() + (m_iBallPowerMeterFillWidth - iFinalSectionWidth), m_pBallPowerMeterFinalSection->GetYPos() ); |
|
} |
|
m_iPrevBallPower = g_pPasstimeLogic |
|
? g_pPasstimeLogic->GetBallPower() |
|
: 0; |
|
|
|
// find the left/right markers in the res files |
|
{ |
|
auto *pRedEnd = FindChildByName( "RedProgressEnd" ); |
|
auto *pBlueEnd = FindChildByName( "BlueProgressEnd" ); |
|
if ( pRedEnd && pBlueEnd ) |
|
{ |
|
pRedEnd->GetPos( m_iXRedProgress, m_iYRedProgress ); |
|
pBlueEnd->GetPos( m_iXBlueProgress, m_iYBlueProgress ); |
|
} |
|
else |
|
{ |
|
// no markers in the res file, just give it offscreen but valid coords |
|
m_iXBlueProgress = -100.0f; |
|
m_iXRedProgress = -100.0f; |
|
m_iYBlueProgress = -100.0f; |
|
m_iYRedProgress = -100.0f; |
|
} |
|
} |
|
|
|
// find all the goal icon image panels |
|
{ |
|
char buf[16]; |
|
for ( auto i = 0; i < NumGoalIcons; ++i ) |
|
{ |
|
V_sprintf_safe( buf, "GoalRed%i", i ); |
|
m_pGoalIconsRed[i] = FindControl<vgui::ImagePanel>( buf ); |
|
V_sprintf_safe( buf, "GoalBlue%i", i ); |
|
m_pGoalIconsBlue[i] = FindControl<vgui::ImagePanel>( buf ); |
|
} |
|
} |
|
|
|
ListenForGameEvent( PasstimeGameEvents::BallFree::s_eventName ); |
|
ListenForGameEvent( PasstimeGameEvents::BallGet::s_eventName ); |
|
ListenForGameEvent( PasstimeGameEvents::BallStolen::s_eventName ); |
|
ListenForGameEvent( PasstimeGameEvents::PassCaught::s_eventName ); |
|
ListenForGameEvent( PasstimeGameEvents::Score::s_eventName ); |
|
Reset(); // this ensures players will try to guess game state for the hud if they join a game in progress |
|
vgui::ivgui()->AddTickSignal( GetVPanel() ); |
|
|
|
// find all the player icon image panels |
|
{ |
|
char controlname[32]; |
|
for ( auto i = 0; i < MAX_PLAYERS; ++i ) |
|
{ |
|
V_sprintf_safe( controlname, "playericon%i", i ); // ugh |
|
m_pPlayerIcons[i] = FindControl<vgui::ImagePanel>( controlname ); |
|
Assert( m_pPlayerIcons[i] ); |
|
m_pPlayerIcons[i]->SetEnabled( true ); |
|
m_pPlayerIcons[i]->SetShouldScaleImage( true ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFHudPasstimeBallStatus::BShouldDraw() const |
|
{ |
|
CBasePlayer *pPlayer = CBasePlayer::GetLocalPlayer(); |
|
if ( !pPlayer || !pPlayer->IsAlive() || ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
gamerules_roundstate_t roundstate = TFGameRules()->State_Get(); |
|
return ((roundstate == GR_STATE_RND_RUNNING) || (roundstate == GR_STATE_STALEMATE)) |
|
&& !TFGameRules()->InSetup(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// FIXME copypasta with tf_passtime_logic.cpp |
|
static float CalcProgressFrac( const Vector& vecOrigin ) |
|
{ |
|
Assert( g_pPasstimeLogic && (g_pPasstimeLogic->GetNumSections() > 0) ); |
|
|
|
Vector arrTrackPoints[16]; |
|
g_pPasstimeLogic->GetTrackPoints( arrTrackPoints ); |
|
|
|
float flBestDist = FLT_MAX; |
|
float flBestLen = 0; |
|
float flTotalLen = 1; // don't set 0 so div by zero is impossible |
|
|
|
Vector vecThisPoint; |
|
Vector vecPointOnLine; |
|
Vector vecPrevPoint = arrTrackPoints[0]; |
|
float flThisFrac = 0; |
|
float flThisLen = 0; |
|
float flThisDist = 0; |
|
for ( int i = 1; i < ARRAYSIZE(arrTrackPoints); ++i ) |
|
{ |
|
vecThisPoint = arrTrackPoints[i]; |
|
if ( vecThisPoint.IsZero() ) |
|
{ |
|
break; |
|
} |
|
flThisLen = (vecThisPoint - vecPrevPoint).Length(); |
|
flTotalLen += flThisLen; |
|
CalcClosestPointOnLineSegment( vecOrigin, vecPrevPoint, vecThisPoint, vecPointOnLine, &flThisFrac ); |
|
flThisDist = (vecPointOnLine - vecOrigin).Length(); |
|
if ( flThisDist < flBestDist ) |
|
{ |
|
flBestDist = flThisDist; |
|
flBestLen = flTotalLen - (flThisLen * (1.0f - flThisFrac)); |
|
} |
|
vecPrevPoint = vecThisPoint; |
|
} |
|
|
|
return (float)(flBestLen / flTotalLen); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// FIXME copypasta with tf_passtime_logic.cpp |
|
static float CalcBallProgressFrac() |
|
{ |
|
Assert( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() && (g_pPasstimeLogic->GetNumSections() > 0) ); |
|
CPasstimeBall *pBall = g_pPasstimeLogic->GetBall(); |
|
CTFPlayer *pCarrier = pBall->GetCarrier(); |
|
return CalcProgressFrac( pCarrier |
|
? pCarrier->GetNetworkOrigin() |
|
: pBall->GetNetworkOrigin() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::UpdateGoalIcon( vgui::ImagePanel *pIcon, C_FuncPasstimeGoal *pGoal ) |
|
{ |
|
Assert( m_bInitialized ); |
|
// TODO: animations on enable/disable? |
|
if ( !pIcon ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( !pGoal ) |
|
{ |
|
pIcon->SetVisible( false ); |
|
return; |
|
} |
|
|
|
pIcon->SetVisible( true ); |
|
|
|
const auto iDisabledAlpha = 50; |
|
const auto iEnabledAlpha = 255; |
|
pIcon->SetAlpha( ( pGoal->BGoalTriggerDisabled() && ( pGoal->GetGoalType() != C_FuncPasstimeGoal::TYPE_TOWER ) ) |
|
? iDisabledAlpha |
|
: iEnabledAlpha ); |
|
|
|
// TODO don't call SetImage(char*) every frame, wtf. |
|
pIcon->SetImage( GetProgressGoalImage( pGoal ) ); |
|
|
|
const float flProgressFrac = CalcProgressFrac( pGoal->GetAbsOrigin() ); |
|
const int iActualBarHalfHeight = m_pProgressLevelBar ? m_pProgressLevelBar->GetTall() / 6 : 0; // magic number because NPOT waste in image |
|
int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - ( pIcon->GetWide() / 2 ); |
|
int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - iActualBarHalfHeight; |
|
pIcon->SetPos( iX, iY ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnTickHidden() |
|
{ |
|
m_bGoalsFound = false; |
|
|
|
if ( m_pProgressBall ) |
|
{ |
|
m_pProgressBall->SetVisible( false ); |
|
} |
|
|
|
if ( m_pProgressBallCarrierName ) |
|
{ |
|
m_pProgressBallCarrierName->SetVisible( false ); |
|
} |
|
|
|
if ( m_pSelfPlayerIcon ) |
|
{ |
|
m_pSelfPlayerIcon->SetVisible( false ); |
|
} |
|
|
|
if ( m_pProgressLevelBar ) |
|
{ |
|
m_pProgressLevelBar->SetVisible( false ); |
|
} |
|
|
|
if ( m_pPowerCluster ) |
|
{ |
|
m_pPowerCluster->SetVisible( false ); |
|
} |
|
|
|
for ( int i = 0; i < MAX_PLAYERS; ++i ) |
|
{ |
|
m_pPlayerIcons[i]->SetVisible( false ); |
|
} |
|
|
|
m_iPrevBallPower = g_pPasstimeLogic |
|
? g_pPasstimeLogic->GetBallPower() |
|
: 0; |
|
|
|
HideGoalIcons(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnTickVisible( C_TFPlayer *pLocalPlayer, C_PasstimeBall *pBall) |
|
{ |
|
if ( m_pProgressLevelBar ) |
|
{ |
|
m_pProgressLevelBar->SetVisible( true ); |
|
} |
|
|
|
// |
|
// Update ball icon |
|
// |
|
float flBallProgressFrac = CalcBallProgressFrac(); |
|
int iX_Ball = m_pProgressBall ? Lerp( flBallProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - ( m_pProgressBall->GetWide() / 2 ) : 0; |
|
int iY_Ball = m_pProgressBall ? Lerp( flBallProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - ( m_pProgressBall->GetTall() / 2 ) : 0; |
|
|
|
if ( m_pProgressBall ) |
|
{ |
|
m_pProgressBall->SetVisible( true ); |
|
m_pProgressBall->SetPos( iX_Ball, iY_Ball ); |
|
} |
|
|
|
// todo setimage from event, not every frame |
|
// todo look in to letting entities manage their own hud elements |
|
CTFPlayer *pCarrier = pBall->GetCarrier(); |
|
int iTeam = pCarrier |
|
? pCarrier->GetTeamNumber() |
|
: pBall->GetTeamNumber(); |
|
if ( m_pProgressBall ) |
|
{ |
|
m_pProgressBall->SetImage( GetProgressBallImageForTeam( iTeam ) ); |
|
} |
|
|
|
// |
|
// Update ball power |
|
// |
|
if ( m_pPowerCluster ) |
|
{ |
|
m_pPowerCluster->SetVisible( true ); |
|
} |
|
{ |
|
// update the power bar |
|
int iCurPower = g_pPasstimeLogic->GetBallPower(); |
|
int iThreshold = tf_passtime_powerball_threshold.GetInt(); |
|
int iAlpha = (iCurPower > iThreshold) |
|
? FLerp( 150, 255, (1 + sin(gpGlobals->curtime*5)) / 2.0f) |
|
: 222; |
|
if ( m_pBallPowerMeterFill ) |
|
{ |
|
m_pBallPowerMeterFill->SetDrawColor( GetTeamColor( iTeam, iAlpha ) ); |
|
} |
|
float flPowerFrac = (iCurPower / 100.f); |
|
if ( m_pBallPowerMeterFillContainer ) |
|
{ |
|
m_pBallPowerMeterFillContainer->SetWide( m_iBallPowerMeterFillWidth * flPowerFrac ); |
|
} |
|
|
|
// show power up/down notifications |
|
// this should really be done with a game event but this is a temp experimental feature for now |
|
bool bWasAboveThreshold = m_iPrevBallPower > iThreshold; |
|
bool bIsAboveThreshold = iCurPower > iThreshold; |
|
m_iPrevBallPower = iCurPower; |
|
if ( bWasAboveThreshold && !bIsAboveThreshold ) |
|
{ |
|
m_pEventText->Clear(); // push everything else out of the way |
|
m_pEventText->EnqueueGeneric( "#Msg_PasstimeEventPowerDownTitle", "#Msg_PasstimeEventPowerDownDetail", "#Msg_PasstimeEventPowerDownBonus" ); |
|
} |
|
else if ( !bWasAboveThreshold && bIsAboveThreshold ) |
|
{ |
|
m_pEventText->Clear(); // push everything else out of the way |
|
m_pEventText->EnqueueGeneric( "#Msg_PasstimeEventPowerUpTitle", "#Msg_PasstimeEventPowerUpDetail", "#Msg_PasstimeEventPowerUpBonus" ); |
|
} |
|
} |
|
|
|
// |
|
// Update player icons |
|
// TODO make less bad |
|
// |
|
C_TFPlayer *pSpecTarget = ToTFPlayer( pLocalPlayer->GetObserverTarget() ); |
|
if ( m_pSelfPlayerIcon ) |
|
{ |
|
m_pSelfPlayerIcon->SetVisible( false ); |
|
} |
|
const int iActualBarHalfHeight = m_pProgressLevelBar ? m_pProgressLevelBar->GetTall() / 7 : 0; // magic number because NPOT waste in image |
|
for ( int iEntIndex = 1; iEntIndex <= MAX_PLAYERS; iEntIndex++ ) |
|
{ |
|
vgui::ImagePanel *pIcon = m_pPlayerIcons[iEntIndex - 1]; |
|
if ( !g_TF_PR->IsConnected( iEntIndex ) ) |
|
{ |
|
pIcon->SetVisible( false ); |
|
continue; |
|
} |
|
|
|
int iEntTeam = g_TF_PR->GetTeam( iEntIndex ); |
|
if ( !g_TF_PR->IsAlive( iEntIndex ) // no dead people; not the same as IsDead |
|
|| ( ( iEntTeam != TF_TEAM_RED ) && ( iEntTeam != TF_TEAM_BLUE) ) ) // no spectators |
|
{ |
|
pIcon->SetVisible( false ); |
|
continue; |
|
} |
|
|
|
// needed for origin |
|
// dormant means "not available on this client right now" |
|
C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iEntIndex ) ); |
|
if ( !pPlayer || pPlayer->IsDormant() ) |
|
{ |
|
pIcon->SetVisible( false ); |
|
continue; |
|
} |
|
|
|
// don't show cloaked spies |
|
bool bSpy = g_TF_PR->GetPlayerClass( iEntIndex ) == TF_CLASS_SPY; |
|
if ( bSpy ) |
|
{ |
|
if ( pPlayer->m_Shared.IsFullyInvisible() ) |
|
{ |
|
pIcon->SetVisible( false ); |
|
continue; |
|
} |
|
|
|
int iDisguiseTeam = pPlayer->m_Shared.GetDisguiseTeam(); |
|
if ( iDisguiseTeam == TF_TEAM_RED || iDisguiseTeam == TF_TEAM_BLUE ) |
|
{ |
|
iEntTeam = iDisguiseTeam; |
|
} |
|
} |
|
|
|
const float flProgressFrac = CalcProgressFrac( pPlayer->GetNetworkOrigin() ); |
|
// Player pips |
|
{ |
|
pIcon->SetVisible( true ); |
|
if ( iEntTeam == TF_TEAM_RED ) |
|
{ |
|
int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (pIcon->GetWide() / 2); |
|
int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - pIcon->GetTall() - iActualBarHalfHeight; |
|
pIcon->SetPos( iX, iY ); |
|
pIcon->SetImage( "../passtime/hud/passtime_ballcontrol_team_red" ); |
|
} |
|
else |
|
{ |
|
int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (pIcon->GetWide() / 2); |
|
int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) + iActualBarHalfHeight; |
|
pIcon->SetPos( iX, iY ); |
|
pIcon->SetImage( "../passtime/hud/passtime_ballcontrol_team_blue" ); |
|
} |
|
} |
|
|
|
// Local player image |
|
if ( g_TF_PR->IsLocalPlayer( iEntIndex ) || ( pLocalPlayer->IsObserver() && ( pPlayer == pSpecTarget ) ) ) |
|
{ |
|
if ( m_pSelfPlayerIcon ) |
|
{ |
|
// TODO don't call SetImage(char*) every frame, wtf. |
|
if ( pLocalPlayer->IsObserver() && pSpecTarget ) |
|
{ |
|
m_pSelfPlayerIcon->SetImage( GetPlayerProgressPortrait( pSpecTarget ) ); |
|
} |
|
else |
|
{ |
|
m_pSelfPlayerIcon->SetImage( GetPlayerProgressPortrait( pLocalPlayer ) ); |
|
} |
|
|
|
int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (m_pSelfPlayerIcon->GetWide() / 2); |
|
int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - m_pSelfPlayerIcon->GetTall() - iActualBarHalfHeight; |
|
m_pSelfPlayerIcon->SetVisible( true ); |
|
m_pSelfPlayerIcon->SetPos( iX, iY ); |
|
} |
|
} |
|
} |
|
|
|
// |
|
// Refresh goal icons if necessary |
|
// |
|
bool bReadyToFindGoals = (g_pPasstimeLogic->GetNumSections() > 0); |
|
if ( !m_bGoalsFound && bReadyToFindGoals ) |
|
{ |
|
// release any existing handles to goals |
|
for ( auto i = 0; i < NumGoalIcons; ++i ) |
|
{ |
|
m_hGoalsBlue[i].Term(); |
|
m_hGoalsRed[i].Term(); |
|
} |
|
|
|
// Update the goal list and pair hud icons with actual goals |
|
const int iMaxSortedGoals = 8; // arbitrary |
|
const auto &goals = C_FuncPasstimeGoal::GetAutoList(); |
|
const int iNumGoals = goals.Count(); |
|
if ( ( iNumGoals > 1 ) && ( iNumGoals < iMaxSortedGoals ) ) |
|
{ |
|
// sort goals by position in world, from blue to red (blue progress is always 0, red is always 1)) |
|
C_FuncPasstimeGoal* sortedgoals[iMaxSortedGoals]; |
|
std::copy( goals.begin(), goals.end(), sortedgoals ); |
|
std::sort( sortedgoals, sortedgoals + iNumGoals, []( C_FuncPasstimeGoal* a, C_FuncPasstimeGoal* b ) |
|
{ |
|
// this is wasteful but it should only happen one frame per round |
|
// The order of the icons in the hud res file determines which direction to sort these |
|
// so that the iteration below visits the goals in the same order. |
|
return CalcProgressFrac( a->GetAbsOrigin() ) < CalcProgressFrac( b->GetAbsOrigin() ); |
|
}); |
|
|
|
// Pair goals and icons so the tick function can update the icon state based on the actual goal state |
|
// This should work for any number of goals and icons in any order. |
|
// If there are more goals than icons, just ignore them, but make sure the hud shows at least |
|
// the goals farthest from the middle of the map in a symmetrical way, so iterate from both ends |
|
// of the array of sorted goals. |
|
|
|
// pair first N blue goals in left-right order |
|
// first icon in array is leftmost blue goal on hud is first goal in sorted array |
|
int iRedIcon = 0; |
|
int iBlueIcon = 0; |
|
for ( int iGoal = 0; iGoal < iNumGoals; ++iGoal ) |
|
{ |
|
// NOTE: goal's teams are backwards: the blue-colored goals on the blue side of the map are |
|
// on team red because that's where red scores. doh. |
|
auto *pGoal = sortedgoals[iGoal]; |
|
if ( pGoal && ( iBlueIcon < NumGoalIcons ) && ( pGoal->GetTeamNumber() == TF_TEAM_RED ) ) |
|
{ |
|
m_hGoalsBlue[iBlueIcon++] = pGoal; |
|
} |
|
|
|
pGoal = sortedgoals[iNumGoals - iGoal - 1]; |
|
if ( pGoal && ( iRedIcon < NumGoalIcons ) && ( pGoal->GetTeamNumber() == TF_TEAM_BLUE ) ) |
|
{ |
|
m_hGoalsRed[iRedIcon++] = pGoal; |
|
} |
|
} |
|
|
|
m_bGoalsFound = true; |
|
} |
|
} |
|
|
|
// |
|
// Update goal icons |
|
// |
|
if ( m_bGoalsFound ) |
|
{ |
|
for ( auto i = 0; i < NumGoalIcons; ++i ) |
|
{ |
|
UpdateGoalIcon( m_pGoalIconsBlue[i], m_hGoalsBlue[i].Get() ); |
|
UpdateGoalIcon( m_pGoalIconsRed[i], m_hGoalsRed[i].Get() ); |
|
} |
|
} |
|
else |
|
{ |
|
HideGoalIcons(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::HideGoalIcons() |
|
{ |
|
for ( int i = 0; i < NumGoalIcons; ++i ) |
|
{ |
|
auto *pIcon = m_pGoalIconsBlue[i]; |
|
if ( pIcon ) |
|
pIcon->SetVisible( false ); |
|
pIcon = m_pGoalIconsRed[i]; |
|
if ( pIcon ) |
|
pIcon->SetVisible( false ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnTick() |
|
{ |
|
if ( !g_pPasstimeLogic || !m_bInitialized ) |
|
{ |
|
// happens randomly during map load |
|
m_pEventText->Clear(); |
|
return; |
|
} |
|
|
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
if ( !pLocalPlayer ) |
|
{ |
|
// can happen during exit |
|
return; |
|
} |
|
|
|
// Tick the event text regardless of the state of the rest of the hud so |
|
// that messages can continue to display even after the round ends or whatever. |
|
m_pEventText->Tick(); |
|
|
|
// I wish there were a reliable way to do this in an event-driven way instead of every frame |
|
C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall(); |
|
if ( !BShouldDraw() || !pBall ) |
|
{ |
|
OnTickHidden(); |
|
|
|
// this is the easiest way I could find to refresh the goals when switching maps |
|
// todo this is dumb |
|
m_bGoalsFound = false; |
|
} |
|
else |
|
{ |
|
OnTickVisible( pLocalPlayer, pBall ); |
|
} |
|
|
|
// |
|
// Try to figure out game state that's usually managed by events if hud was reloaded mid-game |
|
// This happens at the end to ensure that the hud is fully initialized before trying to reset |
|
// on the first frame a player joins a game in progress |
|
// |
|
if ( m_bReset ) |
|
{ |
|
m_pEventText->Clear(); |
|
|
|
// I don't *think* it's possible that this could accidentally end up |
|
// running every frame. |
|
|
|
// Order of operations here is important, see functions for details |
|
// todo make less bad |
|
if ( TryForceBallGet() || TryForceBallFree() ) |
|
{ |
|
m_bReset = false; |
|
} |
|
}; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFHudPasstimeBallStatus::TryForceBallGet() |
|
{ |
|
Assert( m_bInitialized ); |
|
|
|
// The HUD was reset during play, or possibly due to joining a game inprogroess, |
|
// so try to get into a BallGet state if possible |
|
if ( !g_TF_PR ) |
|
{ |
|
// is this even possible? |
|
return false; |
|
} |
|
|
|
for ( int iPlayer = 1 ; iPlayer <= MAX_PLAYERS; iPlayer++ ) |
|
{ |
|
if ( !g_TF_PR->IsConnected( iPlayer ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) ); |
|
if ( !pPlayer || !pPlayer->m_Shared.HasPasstimeBall() ) |
|
{ |
|
continue; |
|
} |
|
|
|
if ( g_TF_PR->IsLocalPlayer( iPlayer ) ) |
|
{ |
|
OnBallGetSelf( iPlayer ); |
|
} |
|
else |
|
{ |
|
OnBallGetOther( iPlayer ); |
|
} |
|
|
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool CTFHudPasstimeBallStatus::TryForceBallFree() |
|
{ |
|
Assert( m_bInitialized ); |
|
|
|
// The HUD was reset during play, or possibly due to joining a game inprogroess, |
|
// so try to get into a BallGet state if possible |
|
if ( !g_pPasstimeLogic ) |
|
{ |
|
return false; |
|
} |
|
|
|
C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall(); |
|
if ( !m_bInitialized || !pBall ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( m_pProgressBallCarrierName ) |
|
{ |
|
m_pProgressBallCarrierName->SetVisible( false ); |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnBallGet( int getterIndex ) |
|
{ |
|
Assert( m_bInitialized && g_PR ); |
|
|
|
if ( g_PR->IsLocalPlayer( getterIndex ) ) |
|
{ |
|
OnBallGetSelf( getterIndex ); |
|
} |
|
else |
|
{ |
|
OnBallGetOther( getterIndex ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::FireGameEvent( IGameEvent *pEvent ) |
|
{ |
|
if ( !m_bInitialized || !g_TF_PR || !g_PR ) |
|
{ |
|
return; |
|
} |
|
|
|
const char *pszEventName = pEvent->GetName(); |
|
if ( FStrEq( pszEventName, PasstimeGameEvents::BallFree::s_eventName ) ) |
|
{ |
|
PasstimeGameEvents::BallFree ev( pEvent ); |
|
C_TFPlayer *pOwner = ToTFPlayer( UTIL_PlayerByIndex( ev.ownerIndex ) ); |
|
C_TFPlayer *pAttacker = ToTFPlayer( UTIL_PlayerByIndex( ev.attackerIndex ) ); |
|
const bool wasMyBall = pOwner == C_TFPlayer::GetLocalPlayer(); |
|
if ( wasMyBall ) |
|
{ |
|
OnBallFreeSelf( pOwner, pAttacker ); |
|
} |
|
else |
|
{ |
|
OnBallFreeOther( pOwner, pAttacker ); |
|
} |
|
} |
|
else if ( FStrEq( pszEventName, PasstimeGameEvents::BallGet::s_eventName ) ) |
|
{ |
|
OnBallGet( PasstimeGameEvents::BallGet( pEvent ).ownerIndex ); |
|
} |
|
else if ( FStrEq( pszEventName, PasstimeGameEvents::BallStolen::s_eventName ) ) |
|
{ |
|
PasstimeGameEvents::BallStolen ballStolenEvent( pEvent ); |
|
OnBallGet( ballStolenEvent.attackerIndex ); |
|
|
|
auto *pAttacker = ToTFPlayer( UTIL_PlayerByIndex(ballStolenEvent.attackerIndex) ); |
|
auto *pVictim = ToTFPlayer( UTIL_PlayerByIndex(ballStolenEvent.victimIndex) ); |
|
m_pEventText->EnqueueSteal( pVictim, pAttacker ); |
|
} |
|
else if ( FStrEq( pszEventName, PasstimeGameEvents::PassCaught::s_eventName ) ) |
|
{ |
|
PasstimeGameEvents::PassCaught passCaughtEvent( pEvent ); |
|
OnBallGet( passCaughtEvent.catcherIndex ); |
|
|
|
auto *pCatcher = ToTFPlayer( UTIL_PlayerByIndex( passCaughtEvent.catcherIndex ) ); |
|
auto *pThrower = ToTFPlayer( UTIL_PlayerByIndex( passCaughtEvent.passerIndex ) ); |
|
if ( pCatcher && pThrower ) |
|
{ |
|
if ( pCatcher->GetTeamNumber() == pThrower->GetTeamNumber() ) |
|
{ |
|
m_pEventText->EnqueuePass( pThrower, pCatcher ); |
|
} |
|
else |
|
{ |
|
m_pEventText->EnqueueInterception( pThrower, pCatcher ); |
|
} |
|
} |
|
} |
|
else if ( FStrEq( pszEventName, PasstimeGameEvents::Score::s_eventName ) ) |
|
{ |
|
OnBallScore(); |
|
PasstimeGameEvents::Score scoreEvent( pEvent ); |
|
auto *pScorer = ToTFPlayer( UTIL_PlayerByIndex( scoreEvent.scorerIndex ) ); |
|
auto *pAssister = ToTFPlayer( UTIL_PlayerByIndex( scoreEvent.assisterIndex ) ); |
|
m_pEventText->EnqueueScore( pScorer, pAssister ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::Reset() |
|
{ |
|
m_bReset = true; |
|
|
|
// delay the actual reset for a bit because Reset is called before |
|
// the game entities are settled into their new state (e.g. the ball |
|
// is about to be deleted). |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnBallFreeOther( C_TFPlayer *pOwner, |
|
C_TFPlayer *pAttacker ) |
|
{ |
|
Assert( m_bInitialized ); |
|
if ( m_pProgressBallCarrierName ) |
|
{ |
|
m_pProgressBallCarrierName->SetVisible( false ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnBallFreeSelf( C_TFPlayer *pOwner, |
|
C_TFPlayer *pAttacker ) |
|
{ |
|
Assert( m_bInitialized ); |
|
if ( m_pProgressBallCarrierName ) |
|
{ |
|
m_pProgressBallCarrierName->SetVisible( false ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnBallGetOther( int iPlayer ) |
|
{ |
|
Assert( m_bInitialized ); |
|
Assert( g_PR ); |
|
|
|
wchar_t wszFinalText[128]; |
|
wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; |
|
wchar_t *pwszFormatString = g_pVGuiLocalize->Find( "#TF_Passtime_CarrierName" ); |
|
if ( !pwszFormatString ) |
|
{ |
|
pwszFormatString = L"%s1"; |
|
} |
|
g_pVGuiLocalize->ConvertANSIToUnicode( ( iPlayer > 0 ) ? g_PR->GetPlayerName( iPlayer ) : "", wszPlayerName, sizeof( wszPlayerName ) ); |
|
g_pVGuiLocalize->ConstructString_safe( wszFinalText, pwszFormatString, 1, wszPlayerName ); |
|
|
|
if ( m_pProgressBallCarrierName ) |
|
{ |
|
m_pProgressBallCarrierName->SetText( wszFinalText ); |
|
m_pProgressBallCarrierName->SetVisible( true ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnBallGetSelf( int iPlayer ) |
|
{ |
|
Assert( m_bInitialized ); |
|
Assert( g_PR ); |
|
|
|
wchar_t wszFinalText[128]; |
|
wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; |
|
wchar_t *pwszFormatString = g_pVGuiLocalize->Find( "#TF_Passtime_CarrierName" ); |
|
if ( !pwszFormatString ) |
|
{ |
|
pwszFormatString = L"%s1"; |
|
} |
|
g_pVGuiLocalize->ConvertANSIToUnicode( ( iPlayer > 0 ) ? g_PR->GetPlayerName( iPlayer ) : "", wszPlayerName, sizeof( wszPlayerName ) ); |
|
g_pVGuiLocalize->ConstructString_safe( wszFinalText, pwszFormatString, 1, wszPlayerName ); |
|
|
|
if ( m_pProgressBallCarrierName ) |
|
{ |
|
m_pProgressBallCarrierName->SetText( wszFinalText ); |
|
m_pProgressBallCarrierName->SetVisible( true ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstimeBallStatus::OnBallScore() |
|
{ |
|
Assert( m_bInitialized ); |
|
if ( m_pProgressBallCarrierName ) |
|
{ |
|
m_pProgressBallCarrierName->SetVisible( false ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CTFHudPasstime |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstime::CTFHudPasstime( Panel *pParent ) |
|
: CTFHudPasstimePanel( pParent, "HudPasstime" ) |
|
, m_pBallStatus( new CTFHudPasstimeBallStatus( this ) ) |
|
, m_pTeamScore( new CTFHudTeamScore( this ) ) |
|
, m_pBallOffscreenArrow( new CTFHudPasstimeBallOffscreenArrow( this ) ) |
|
, m_pPassNotify( new CTFHudPasstimePassNotify( this ) ) |
|
{ |
|
for ( int i = 0; i < MAX_PLAYERS; ++i ) |
|
{ |
|
m_pPlayerArrows[i] = new CTFHudPasstimePlayerOffscreenArrow( this, i ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFHudPasstime::~CTFHudPasstime() |
|
{ |
|
// this is only called when the game quits, so don't bother deleting members |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstime::ApplySchemeSettings( IScheme *pScheme ) |
|
{ |
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
LoadControlSettings( "resource/UI/HudPasstime.res", NULL, NULL, NULL ); |
|
vgui::ivgui()->AddTickSignal( GetVPanel() ); |
|
OnTick(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstime::Reset() |
|
{ |
|
if ( m_pBallStatus ) |
|
{ |
|
m_pBallStatus->Reset(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFHudPasstime::OnTick() |
|
{ |
|
if ( m_pTeamScore ) |
|
{ |
|
m_pTeamScore->SetVisible( IsVisible() ); |
|
} |
|
}
|
|
|