Adding the Game Instructor

From Valve Developer Community
Jump to: navigation, search
Español
The "Game Instructor" allows you to display instructions while playing.

The Game Instructor is, more or less, a clientside system focused on showing instructions on how to play or perform certain actions during gameplay. It made its first appearance in the Left 4 Dead branch of the engine and has been featured in all major Valve titles since then.

This tutorial will be going over on how to incorporate the Game Instructor into Source SDK 2013 (it may work on other branches, but none other has been tested). The code was derived from Alien Swarm with some modifications to make it work within Source SDK 2013.

To do: Complete the tutorial...

Requirements

CLocatorTarget

The class CLocatorTarget of the files hud_locator_target.cpp/hud_locator_target.h is responsible for displaying the messages on the player screen (Client) and (in case of addressing an object, for example a button) to move it to the location of the object to be used (That's where its name comes from). So to begin with we will need to incorporate the following 2 files into the Client's project:

hud_locator_target.h

//====== Copyright © 1996-2008, Valve Corporation, All rights reserved. =======
//
// Purpose: Add entities to this system, and the Locator will maintain an arrow
//			on the HUD that points to the entities when they are offscreen.
//
//=============================================================================

#ifndef L4D_HUD_LOCATOR_H
#define L4D_HUD_LOCATOR_H
#ifdef _WIN32
#pragma once
#endif


#include "vgui_controls/PHandle.h"


#define MAX_LOCATOR_BINDINGS_SHOWN	8
#define MAX_LOCATOR_TARGETS			10
#define LOCATOR_FLAGS_NONE			0x00000000

#define LOCATOR_ICON_FX_NONE			0x00000000
#define LOCATOR_ICON_FX_PULSE_SLOW		0x00000001
#define LOCATOR_ICON_FX_PULSE_FAST		0x00000002
#define LOCATOR_ICON_FX_PULSE_URGENT	0x00000004
#define LOCATOR_ICON_FX_ALPHA_SLOW		0x00000008
#define LOCATOR_ICON_FX_ALPHA_FAST		0x00000010
#define LOCATOR_ICON_FX_ALPHA_URGENT	0x00000020
#define LOCATOR_ICON_FX_SHAKE_NARROW	0x00000040
#define LOCATOR_ICON_FX_SHAKE_WIDE		0x00000080
#define LOCATOR_ICON_FX_STATIC			0x00000100	// This icon draws at a fixed location on the HUD.
#define LOCATOR_ICON_FX_NO_OFFSCREEN	0x00000200
#define LOCATOR_ICON_FX_FORCE_CAPTION	0x00000400	// Always draw the caption, even when the icon is occluded.
#define LOCATOR_ICON_FX_FADE_OUT		0x00000800	// Set when deactivated so it can smoothly vanish
#define LOCATOR_ICON_FX_FADE_IN			0x00001000	// Set when activated so it can smoothly appear

#include "tier1/UtlSymbol.h"

// See comments in UtlSymbol on why this is useful
DECLARE_PRIVATE_SYMBOLTYPE( CGameInstructorSymbol );

//-----------------------------------------------------------------------------
// This class represents a single target to be tracked by the locator
//-----------------------------------------------------------------------------
class CLocatorTarget
{
public:
	bool		m_bOriginInScreenspace;
	Vector		m_vecOrigin;			// The location in the world to draw on the locator

	// ONLY the locator panel should fiddle with these fields.
	bool		m_isActive;		
	int			m_serialNumber;
	int			m_frameLastUpdated;
	bool		m_bOnscreen;
	bool		m_bOccluded;
	bool		m_bVisible;
	bool		m_bIsDrawing;
	float		m_distFromPlayer;
	CHudTexture	*m_pIcon_onscreen;
	CHudTexture	*m_pIcon_offscreen;
	int			m_iBindingTick;
	float		m_flNextBindingTick;
	float		m_flNextOcclusionTest;
	int			m_iBindingChoicesCount;
	const char	*(m_pchBindingChoices[ MAX_LOCATOR_BINDINGS_SHOWN ]);
	int			m_iBindChoicesOriginalToken[ MAX_LOCATOR_BINDINGS_SHOWN ];

	// Fields for drawing
	int			m_targetX;				// screen X position of the actual target
	int			m_targetY;				// screen Y position of the actual target
	int			m_iconX;				// screen X position (top)
	int			m_iconY;				// screen Y position (left)
	int			m_centerX;				// screen X position (center)
	int			m_centerY;				// screen Y position (center)
	int			m_wide;					// draw width of icon (may be different from frame to frame as the icon's size animates, for instance)
	int			m_tall;					// draw height of icon  ''			''
	float		m_widthScale_onscreen;	// for icons that are wider than standard
	int			m_alpha;				// 
	float		m_fadeStart;			// time stamp when fade out started
	float		m_lerpStart;			// time stamp when lerping started
	float		m_pulseStart;			// time stamp when pulsing started
	int			m_declutterIndex;		// sort order from the declutterer
	int			m_lastDeclutterIndex;	// last sort order from the declutterer
	int			m_drawArrowDirection;	// Whether to draw an arrow indicating this target is off-screen, also tells us which arrow to draw (left, up, etc.)
	int			m_captionWide;			// How wide (pixels) my caption is.
	bool		m_bDrawControllerButton;
	bool		m_bDrawControllerButtonOffscreen;
	int			m_offsetX;				// User-specified X offset which is applied in screenspace
	int			m_offsetY;				// User-specified Y offset which is applied in screenspace

	// Fields for interpolating icon position
	float		m_flTimeLerpDone;		// How much time left before this icon arrives where it is supposed to be.
	int			m_lastXPos;				// screen X position last frame
	int			m_lastYPos;				// ''     Y

	CLocatorTarget( void );
	void Activate( int serialNumber );
	void Deactivate( bool bNoFade = false );
	void Update();

	int GetIconX( void );
	int GetIconY( void );
	int GetIconCenterX( void );
	int GetIconCenterY( void );
	int GetIconWidth( void );
	int GetIconHeight( void );

	void AddIconEffects( int add )			{ m_iEffectsFlags |= add; }
	void RemoveIconEffects( int remove )	{ m_iEffectsFlags &= ~remove; }
	int GetIconEffectsFlags()				{ return m_iEffectsFlags; }
	void SetCaptionColor( Color col )		{ m_captionColor = col; }
	void SetCaptionColor( const char *pszCaptionColor );
	bool IsStatic();
	bool IsPresenting();
	void StartTimedLerp();
	void StartPresent();
	void EndPresent();

	void UpdateVguiTarget( void );
	vgui::Panel *GetVguiTarget( void );
	void SetVguiTargetName( const char *pchVguiTargetName );
	const char *GetVguiTargetName( void ) { return m_szVguiTargetName.String(); }
	void SetVguiTargetLookup( const char *pchVguiTargetLookup );
	const char *GetVguiTargetLookup( void ) { return m_szVguiTargetLookup.String(); }
	void SetVguiTargetEdge( int nVguiEdge );
	int GetVguiTargetEdge( void ) const { return m_nVguiTargetEdge; }

	void SetOnscreenIconTextureName( const char *pszTexture );
	void SetOffscreenIconTextureName( const char *pszTexture );
	void SetBinding( const char *pszBinding );
	const char *UseBindingImage( char *pchIconTextureName, size_t bufSize );

	const char *GetOnscreenIconTextureName()	{ return m_szOnscreenTexture.String(); }
	const char *GetOffscreenIconTextureName()	{ return m_szOffscreenTexture.String(); }
	const char *GetBinding()			{ return m_szBinding.String(); }

	void SetVisible( bool bVisible );
	bool IsVisible( void );

	void SetCaptionText( const char *pszText, const char *pszParam );
	const wchar_t *GetCaptionText( void )	{ return (const wchar_t *)m_wszCaption.Base(); }
	bool HasCaptionText( void )			{ return m_wszCaption.Count() > 1; }

	void DrawBindingName( const char *pchDrawName )		{ m_pchDrawBindingName = pchDrawName; }
	void DrawBindingNameOffscreen( const char *pchDrawName )	{ m_pchDrawBindingNameOffscreen = pchDrawName; }

	const char *DrawBindingName( void )				{ return m_pchDrawBindingName; }
	const char *DrawBindingNameOffscreen( void )	{ return m_pchDrawBindingNameOffscreen; }

	bool IsOnScreen()	{ return m_bOnscreen; }
	bool IsOccluded()	{ return m_bOccluded; }


private:
	CGameInstructorSymbol		m_szVguiTargetName;
	CGameInstructorSymbol		m_szVguiTargetLookup;
	vgui::DHANDLE<vgui::Panel>	m_hVguiTarget;
	int							m_nVguiTargetEdge;

	CGameInstructorSymbol	m_szOnscreenTexture;
	CGameInstructorSymbol	m_szOffscreenTexture;
	CGameInstructorSymbol	m_szBinding;

	bool		m_bWasControllerLast;
	const char	*m_pchDrawBindingName;
	const char	*m_pchDrawBindingNameOffscreen;
	int			m_iEffectsFlags;
	CUtlVector< wchar_t > m_wszCaption;

public:
	Color		m_captionColor;
};

extern int Locator_AddTarget();
extern void Locator_RemoveTarget( int hTarget );
CLocatorTarget *Locator_GetTargetFromHandle( int hTarget );
void Locator_ComputeTargetIconPositionFromHandle( int hTarget );


#endif // L4D_HUD_LOCATOR_H

hud_locator_target.cpp

//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose: See header file
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "hud_locator_target.h"
#include "iclientmode.h"
#include <vgui/ILocalize.h>
#include <vgui/ISurface.h>
#include <vgui/IVGUI.h>
#include <vgui_controls/EditablePanel.h>
#include <vgui_controls/Controls.h>
#include <vgui_controls/Label.h>
#include <vgui/IInput.h>
#include <vgui/IScheme.h>
#include "iinput.h"
#include "view.h"
#include "hud.h"
#include "hudelement.h"
#include "vgui_int.h"

#include "hud_macros.h"
#include "iclientmode.h"

#ifdef INSOURCE
#include "in_player_shared.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


#define ICON_SIZE			0.04f	// Icons are ScreenWidth() * ICON_SIZE wide.
#define ICON_GAP			5		// Number of pixels between the icon and the text

#define OFFSCREEN_ICON_POSITION_RADIUS 100
#define BUTTON_FONT_HANDLE				m_hCaptionFont

#define ICON_DIST_TOO_FAR	(60.0f * 12.0f)

#define MIN_ICON_ALPHA 0.5
#define MAX_ICON_ALPHA 1

ConVar locator_icon_min_size_non_ss( "locator_icon_min_size_non_ss", "1.0", FCVAR_NONE, "Minimum scale of the icon on the screen" );
ConVar locator_icon_max_size_non_ss( "locator_icon_max_size_non_ss", "1.5", FCVAR_NONE, "Maximum scale of the icon on the screen" );

#define MIN_ICON_SCALE			locator_icon_min_size_non_ss.GetFloat()
#define MAX_ICON_SCALE			locator_icon_max_size_non_ss.GetFloat()

#define LOCATOR_OCCLUSION_TEST_RATE 0.25f

enum
{
	DRAW_ARROW_NO = 0,
	DRAW_ARROW_UP,
	DRAW_ARROW_DOWN,
	DRAW_ARROW_LEFT,
	DRAW_ARROW_RIGHT
};

ConVar locator_fade_time( "locator_fade_time", "0.3", FCVAR_NONE, "Number of seconds it takes for a lesson to fully fade in/out." );
ConVar locator_lerp_speed( "locator_lerp_speed", "5.0f", FCVAR_NONE, "Speed that static lessons move along the Y axis." );
ConVar locator_lerp_rest( "locator_lerp_rest", "2.25f", FCVAR_NONE, "Number of seconds before moving from the center." );
ConVar locator_lerp_time( "locator_lerp_time", "1.75f", FCVAR_NONE, "Number of seconds to lerp before reaching final destination" );
ConVar locator_pulse_time( "locator_pulse_time", "1.0f", FCVAR_NONE, "Number of seconds to pulse after changing icon or position" );
ConVar locator_start_at_crosshair( "locator_start_at_crosshair", "0", FCVAR_NONE, "Start position at the crosshair instead of the top middle of the screen." );

ConVar locator_topdown_style( "locator_topdown_style", "0", FCVAR_NONE, "Topdown games set this to handle distance and offscreen location differently." );

ConVar locator_background_style( "locator_background_style", "0", FCVAR_NONE, "Setting this to 1 will show rectangle backgrounds behind the items word-bubble pointers." );
ConVar locator_background_color( "locator_background_color", "255 255 255 5", FCVAR_NONE, "The default color for the background." );
ConVar locator_background_border_color( "locator_background_border_color", "255 255 255 15", FCVAR_NONE, "The default color for the border." );
ConVar locator_background_thickness_x( "locator_background_thickness_x", "8", FCVAR_NONE, "How many pixels the background borders the left and right." );
ConVar locator_background_thickness_y( "locator_background_thickness_y", "0", FCVAR_NONE, "How many pixels the background borders the top and bottom." );
ConVar locator_background_shift_x( "locator_background_shift_x", "3", FCVAR_NONE, "How many pixels the background is shifted right." );
ConVar locator_background_shift_y( "locator_background_shift_y", "1", FCVAR_NONE, "How many pixels the background is shifted down." );
ConVar locator_background_border_thickness( "locator_background_border_thickness", "3", FCVAR_NONE, "How many pixels the background borders the left and right." );

ConVar locator_target_offset_x( "locator_target_offset_x", "0", FCVAR_NONE, "How many pixels to offset the locator from the target position." );
ConVar locator_target_offset_y( "locator_target_offset_y", "0", FCVAR_NONE, "How many pixels to offset the locator from the target position." );

ConVar locator_text_drop_shadow( "locator_text_drop_shadow", "1", FCVAR_NONE, "If enabled, a drop shadow is drawn behind caption text.  PC only." );
ConVar locator_text_glow( "locator_text_glow", "0", FCVAR_NONE, "If enabled, a glow is drawn behind caption text" );
ConVar locator_text_glow_color( "locator_text_glow_color", "255 255 255 255", FCVAR_NONE, "Color of text glow" );

ConVar locator_split_maxwide_percent( "locator_split_maxwide_percent", "0.80f", FCVAR_CHEAT );
ConVar locator_split_len( "locator_split_len", "0.5f", FCVAR_CHEAT );


//------------------------------------
CLocatorTarget::CLocatorTarget( void )
{
	Deactivate( true );

	PrecacheMaterial("vgui/hud/icon_arrow_left");
	PrecacheMaterial("vgui/hud/icon_arrow_right");
	PrecacheMaterial("vgui/hud/icon_arrow_up");
	PrecacheMaterial("vgui/hud/icon_arrow_down");
	PrecacheMaterial("vgui/hud/icon_arrow_plain");
}

//------------------------------------
void CLocatorTarget::Activate( int serialNumber )
{ 
	m_serialNumber		= serialNumber; 
	m_frameLastUpdated	= gpGlobals->framecount;
	m_isActive			= true;

	m_bVisible			= true;
	m_bOnscreen			= true;
	m_alpha				= 0;
	m_fadeStart			= gpGlobals->curtime;

	m_offsetX			= m_offsetY = 0;

	int iStartX = ScreenWidth() / 2;
	int iStartY = ScreenHeight() / 4;

	// We want to start lessons at the players crosshair, cause that's where they're looking!
	if ( locator_start_at_crosshair.GetBool() )
		vgui::input()->GetCursorPos( iStartX, iStartY );

	m_lastXPos = iStartX;
	m_lastYPos = iStartY;

	m_drawArrowDirection	= DRAW_ARROW_NO;
	m_lerpStart				= gpGlobals->curtime;
	m_pulseStart			= gpGlobals->curtime;
	m_declutterIndex		= 0;
	m_lastDeclutterIndex	= 0;

	AddIconEffects(LOCATOR_ICON_FX_FADE_IN);
}

//------------------------------------
void CLocatorTarget::Deactivate( bool bNoFade )						
{ 
	if ( bNoFade || m_alpha == 0 || 
		 ( m_bOccluded && !( m_iEffectsFlags & LOCATOR_ICON_FX_FORCE_CAPTION ) ) || 
		 ( !m_bOnscreen && ( m_iEffectsFlags & LOCATOR_ICON_FX_NO_OFFSCREEN ) ) )
	{
		m_bOriginInScreenspace = false;

		m_serialNumber			= -1;
		m_isActive				= false;
		m_frameLastUpdated		= 0;
		m_pIcon_onscreen		= NULL;
		m_pIcon_offscreen		= NULL;
		m_bDrawControllerButton = false;
		m_bDrawControllerButtonOffscreen = false;
		m_iEffectsFlags			= LOCATOR_ICON_FX_NONE;
		m_captionWide			= 0;

		m_pchDrawBindingName	= NULL;
		m_pchDrawBindingNameOffscreen = NULL;
		m_widthScale_onscreen	= 1.0f;
		m_bOccluded				= false;
		m_alpha					= 0;
		m_bIsDrawing			= false;
		m_bVisible				= false;

		m_szVguiTargetName		= "";
		m_szVguiTargetLookup	= "";
		m_hVguiTarget			= NULL;
		m_nVguiTargetEdge		= vgui::Label::a_northwest;

		m_szBinding				= "";
		m_iBindingTick			= 0;
		m_flNextBindingTick		= 0.0f;
		m_flNextOcclusionTest	= 0.0f;
		m_iBindingChoicesCount	= 0;

		m_wszCaption.RemoveAll();
		m_wszCaption.AddToTail( (wchar_t)0 );
	}
	else if ( !( m_iEffectsFlags & LOCATOR_ICON_FX_FADE_OUT ) )
	{
		// Determine home much time it would have spent fading to reach the current alpha
		float flAssumedFadeTime;
		flAssumedFadeTime = ( 1.0f - static_cast<float>( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat();

		// Set the fade
		m_fadeStart = gpGlobals->curtime - flAssumedFadeTime;
		AddIconEffects( LOCATOR_ICON_FX_FADE_OUT );
		RemoveIconEffects( LOCATOR_ICON_FX_FADE_IN );
	}
}

//------------------------------------
void CLocatorTarget::Update()							
{
	m_frameLastUpdated = gpGlobals->framecount;

	if ( m_bVisible && ( m_iEffectsFlags & LOCATOR_ICON_FX_FADE_OUT ) )
	{
		// Determine home much time it would have spent fading to reach the current alpha
		float flAssumedFadeTime;
		flAssumedFadeTime = ( 1.0f - static_cast<float>( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat();

		// Set the fade
		m_fadeStart = gpGlobals->curtime - flAssumedFadeTime;
		AddIconEffects( LOCATOR_ICON_FX_FADE_OUT );
		RemoveIconEffects( LOCATOR_ICON_FX_FADE_OUT );
	}
}

int CLocatorTarget::GetIconX( void )
{
	return m_iconX + ( IsOnScreen() ? locator_target_offset_x.GetInt()+m_offsetX : 0 );
}

int CLocatorTarget::GetIconY( void )
{
	return m_iconY + ( IsOnScreen() ? locator_target_offset_y.GetInt()+m_offsetY : 0 );
}

int CLocatorTarget::GetIconCenterX( void )
{
	return m_centerX + locator_target_offset_x.GetInt() + m_offsetX;
}

int CLocatorTarget::GetIconCenterY( void )
{
	return m_centerY + locator_target_offset_y.GetInt() + m_offsetY;
}

void CLocatorTarget::SetVisible( bool bVisible )
{
	// They are already the same
	if ( m_bVisible == bVisible )
		return;

	m_bVisible = bVisible;

	if ( bVisible )
	{
		// Determine home much time it would have spent fading to reach the current alpha
		float flAssumedFadeTime;
		flAssumedFadeTime = ( static_cast<float>( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat();

		// Set the fade
		m_fadeStart = gpGlobals->curtime - flAssumedFadeTime;
		AddIconEffects( LOCATOR_ICON_FX_FADE_IN );
		RemoveIconEffects( LOCATOR_ICON_FX_FADE_OUT );
	}
	else
	{
		// Determine home much time it would have spent fading to reach the current alpha
		float flAssumedFadeTime;
		flAssumedFadeTime = ( 1.0f - static_cast<float>( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat();

		// Set the fade
		m_fadeStart = gpGlobals->curtime - flAssumedFadeTime;
		AddIconEffects( LOCATOR_ICON_FX_FADE_OUT );
		RemoveIconEffects( LOCATOR_ICON_FX_FADE_IN );
	}
}

bool CLocatorTarget::IsVisible( void )
{
	return m_bVisible;
}

void CLocatorTarget::SetCaptionText( const char *pszText, const char *pszParam )
{
	wchar_t outbuf[ 256 ];
	outbuf[ 0 ] = L'\0';

	if ( pszParam && pszParam[ 0 ] != '\0' )
	{
		wchar_t wszParamBuff[ 128 ];
		wchar_t *pLocalizedParam = NULL;

		if ( pszParam[ 0 ] == '#' )
		{
			pLocalizedParam = g_pVGuiLocalize->Find( pszParam );
		}

		if ( !pLocalizedParam )
		{
			g_pVGuiLocalize->ConvertANSIToUnicode( pszParam, wszParamBuff, sizeof( wszParamBuff ) );
			pLocalizedParam = wszParamBuff;
		}

		wchar_t wszTextBuff[ 128 ];
		wchar_t *pLocalizedText = NULL;

		if ( pszText[ 0 ] == '#' )
			pLocalizedText = g_pVGuiLocalize->Find( pszText );

		if ( !pLocalizedText )
		{
			g_pVGuiLocalize->ConvertANSIToUnicode( pszText, wszTextBuff, sizeof( wszTextBuff ) );
			pLocalizedText = wszTextBuff;
		}

		wchar_t buf[ 256 ];
		g_pVGuiLocalize->ConstructString( buf, sizeof(buf), pLocalizedText, 1, pLocalizedParam );

		UTIL_ReplaceKeyBindings( buf, sizeof( buf ), outbuf, sizeof( outbuf ) );
	}
	else
	{
		wchar_t wszTextBuff[ 128 ];
		wchar_t *pLocalizedText = NULL;

		if ( pszText[ 0 ] == '#' )
		{
			pLocalizedText = g_pVGuiLocalize->Find( pszText );
		}

		if ( !pLocalizedText )
		{
			g_pVGuiLocalize->ConvertANSIToUnicode( pszText, wszTextBuff, sizeof( wszTextBuff ) );
			pLocalizedText = wszTextBuff;
		}

		wchar_t buf[ 256 ];
		Q_wcsncpy( buf, pLocalizedText, sizeof( buf ) );

		UTIL_ReplaceKeyBindings( buf, sizeof(buf), outbuf, sizeof( outbuf ) );
	}

	int len = wcslen( outbuf ) + 1;
	m_wszCaption.RemoveAll();
	m_wszCaption.EnsureCount( len );
	Q_wcsncpy( m_wszCaption.Base(), outbuf, len * sizeof( wchar_t ) );
}

void CLocatorTarget::SetCaptionColor( const char *pszCaptionColor )
{
	int r,g,b;
	r = g = b = 0;

	CSplitString colorValues( pszCaptionColor, "," );

	if( colorValues.Count() == 3 )
	{
		r = atoi( colorValues[0] );
		g = atoi( colorValues[1] );
		b = atoi( colorValues[2] );

		m_captionColor.SetColor( r,g,b, 255 );
	}
	else
	{
		DevWarning( "caption_color format incorrect. RRR,GGG,BBB expected.\n");
	}
}

bool CLocatorTarget::IsStatic()
{
	return ( ( m_iEffectsFlags & LOCATOR_ICON_FX_STATIC ) || IsPresenting() );
}

bool CLocatorTarget::IsPresenting()
{
	return ( gpGlobals->curtime - m_lerpStart < locator_lerp_rest.GetFloat() );
}

void CLocatorTarget::StartTimedLerp()
{
	if ( gpGlobals->curtime - m_lerpStart > locator_lerp_rest.GetFloat() )
	{
		m_lerpStart = gpGlobals->curtime - locator_lerp_rest.GetFloat();
	}
}

void CLocatorTarget::StartPresent()
{
	m_lerpStart = gpGlobals->curtime;
}


void CLocatorTarget::EndPresent()
{
	if ( gpGlobals->curtime - m_lerpStart < locator_lerp_rest.GetFloat() )
	{
		m_lerpStart = gpGlobals->curtime - locator_lerp_rest.GetFloat();
	}
}

void CLocatorTarget::UpdateVguiTarget( void )
{
	const char *pchVguiTargetName = m_szVguiTargetName.String();

	if ( !pchVguiTargetName || pchVguiTargetName[ 0 ] == '\0' )
	{
		m_hVguiTarget = NULL;
		return;
	}

	// Get the appropriate token based on the binding
	if ( m_iBindingChoicesCount > 0 )
	{
		int nTagetToken = m_iBindChoicesOriginalToken[ m_iBindingTick % m_iBindingChoicesCount ];

		for ( int nToken = 0; nToken < nTagetToken && pchVguiTargetName; ++nToken )
		{
			pchVguiTargetName = strchr( pchVguiTargetName, ';' );

			if ( pchVguiTargetName )
			{
				pchVguiTargetName++;
			}
		}

		if ( !pchVguiTargetName || pchVguiTargetName[ 0 ] == '\0' )
		{
			// There wasn't enough tokens, just use the first
			pchVguiTargetName = m_szVguiTargetName.String();
		}
	}

	m_hVguiTarget = g_pClientMode->GetViewport();
}

void CLocatorTarget::SetVguiTargetName( const char *pchVguiTargetName )
{
	if ( Q_strcmp( m_szVguiTargetName.String(), pchVguiTargetName ) == 0 )
		return;

	m_szVguiTargetName = pchVguiTargetName;

	UpdateVguiTarget();
}

void CLocatorTarget::SetVguiTargetLookup( const char *pchVguiTargetLookup )
{
	m_szVguiTargetLookup = pchVguiTargetLookup;
}

void CLocatorTarget::SetVguiTargetEdge( int nVguiEdge )
{
	m_nVguiTargetEdge = nVguiEdge;
}

vgui::Panel *CLocatorTarget::GetVguiTarget( void )
{
	return (vgui::Panel *)m_hVguiTarget.Get();
}

//------------------------------------
void CLocatorTarget::SetOnscreenIconTextureName( const char *pszTexture )
{
	if ( Q_strcmp( m_szOnscreenTexture.String(), pszTexture ) == 0 )
		return;

	m_szOnscreenTexture = pszTexture;
	m_pIcon_onscreen	= NULL; // Dirty the onscreen icon so that the Locator will look up the new icon by name.
	m_pulseStart		= gpGlobals->curtime;
}

//------------------------------------
void CLocatorTarget::SetOffscreenIconTextureName( const char *pszTexture )
{
	if ( Q_strcmp( m_szOffscreenTexture.String(), pszTexture ) == 0 )
		return;

	m_szOffscreenTexture	= pszTexture;
	m_pIcon_offscreen		= NULL; // Ditto
	m_pulseStart			= gpGlobals->curtime;
}

//------------------------------------
void CLocatorTarget::SetBinding( const char *pszBinding )	
{
	int iAllowJoystick = -1;

	/*if ( !IsX360() )
	{
		// Only show joystick binds if it's enabled and non-joystick if it's disabled
		iAllowJoystick = input->ControllerModeActive();
	}*/

	bool bIsControllerNow = ( iAllowJoystick != 0 );

	if ( m_bWasControllerLast == bIsControllerNow )
	{
		// We haven't toggled joystick enabled recently, so if it's the same bind, bail
		if ( Q_strcmp( m_szBinding.String(), pszBinding ) == 0 )
			return;
	}

	m_bWasControllerLast = bIsControllerNow;

	m_szBinding			= pszBinding;
	m_pIcon_onscreen	= NULL; // Dirty the onscreen icon so that the Locator will look up the new icon by name.
	m_pIcon_offscreen	= NULL; // ditto.
	m_flNextBindingTick = gpGlobals->curtime + 0.75f;

	// Get a list of all the keys bound to these actions
	m_iBindingChoicesCount = 0;

	// Tokenize the binding name (could be more than one binding)
	int nOriginalToken		= 0;
	const char	*pchToken	= m_szBinding.String();
	char		szToken[ 128 ];

	pchToken = nexttoken( szToken, pchToken, ';' );

	while ( pchToken )
	{
		// Get the first parameter
		int iTokenBindingCount = 0;
		const char *pchBinding = engine->Key_LookupBindingExact( szToken );

		while ( m_iBindingChoicesCount < MAX_LOCATOR_BINDINGS_SHOWN && pchBinding )
		{
			m_pchBindingChoices[ m_iBindingChoicesCount ]			= pchBinding;
			m_iBindChoicesOriginalToken[ m_iBindingChoicesCount ]	= nOriginalToken;
			++m_iBindingChoicesCount;
			++iTokenBindingCount;

			pchBinding = engine->Key_LookupBindingExact( szToken );
		}

		nOriginalToken++;
		pchToken = nexttoken( szToken, pchToken, ';' );
	}

	m_pulseStart = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *CLocatorTarget::UseBindingImage( char *pchIconTextureName, size_t bufSize )
{
	if ( m_iBindingChoicesCount <= 0 )
	{
		if ( IsX360() )
		{
			Q_strncpy( pchIconTextureName, "icon_blank", bufSize );
		}
		else
		{
			Q_strncpy( pchIconTextureName, "icon_key_wide", bufSize );
			return "#GameUI_Icons_NONE";
		}

		return NULL;
	}

	// Cycle through the list of binds at a rate of 2 per second
	const char *pchBinding = m_pchBindingChoices[ m_iBindingTick % m_iBindingChoicesCount ];

	// We counted at least one binding... this should not be NULL!
	Assert( pchBinding );

	if ( IsX360() )
	{
		// Use a blank background for the button icons
		Q_strncpy( pchIconTextureName, "icon_blank", bufSize );
		return pchBinding;
	}

	/*if ( input->ControllerModeActive() && 
		 ( Q_strcmp( pchBinding, "A_BUTTON" ) == 0 || 
		   Q_strcmp( pchBinding, "B_BUTTON" ) == 0 || 
		   Q_strcmp( pchBinding, "X_BUTTON" ) == 0 || 
		   Q_strcmp( pchBinding, "Y_BUTTON" ) == 0 || 
		   Q_strcmp( pchBinding, "L_SHOULDER" ) == 0 || 
		   Q_strcmp( pchBinding, "R_SHOULDER" ) == 0 || 
		   Q_strcmp( pchBinding, "L_TRIGGER" ) == 0 || 
		   Q_strcmp( pchBinding, "R_TRIGGER" ) == 0 || 
		   Q_strcmp( pchBinding, "BACK" ) == 0 || 
		   Q_strcmp( pchBinding, "START" ) == 0 || 
		   Q_strcmp( pchBinding, "STICK1" ) == 0 || 
		   Q_strcmp( pchBinding, "STICK2" ) == 0 || 
		   Q_strcmp( pchBinding, "UP" ) == 0 || 
		   Q_strcmp( pchBinding, "DOWN" ) == 0 || 
		   Q_strcmp( pchBinding, "LEFT" ) == 0 || 
		   Q_strcmp( pchBinding, "RIGHT" ) == 0 ) )
	{
		// Use a blank background for the button icons
		Q_strncpy( pchIconTextureName, "icon_blank", bufSize );
		return pchBinding;
	}*/

	if ( Q_strcmp( pchBinding, "MOUSE1" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_mouseLeft", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "MOUSE2" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_mouseRight", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "MOUSE3" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_mouseThree", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "MWHEELUP" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_mouseWheel_up", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "MWHEELDOWN" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_mouseWheel_down", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "UPARROW" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_key_up", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "LEFTARROW" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_key_left", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "DOWNARROW" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_key_down", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "RIGHTARROW" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_key_right", bufSize );
		return NULL;
	}
	else if ( Q_strcmp( pchBinding, "SEMICOLON" ) == 0 || 
		Q_strcmp( pchBinding, "INS" ) == 0 || 
		Q_strcmp( pchBinding, "DEL" ) == 0 || 
		Q_strcmp( pchBinding, "HOME" ) == 0 || 
		Q_strcmp( pchBinding, "END" ) == 0 || 
		Q_strcmp( pchBinding, "PGUP" ) == 0 || 
		Q_strcmp( pchBinding, "PGDN" ) == 0 || 
		Q_strcmp( pchBinding, "PAUSE" ) == 0 || 
		Q_strcmp( pchBinding, "F10" ) == 0 || 
		Q_strcmp( pchBinding, "F11" ) == 0 || 
		Q_strcmp( pchBinding, "F12" ) == 0 )
	{
		Q_strncpy( pchIconTextureName, "icon_key_generic", bufSize );
		return pchBinding;
	}
	else if ( Q_strlen( pchBinding ) <= 2 )
	{
		Q_strncpy( pchIconTextureName, "icon_key_generic", bufSize );
		return pchBinding;
	}
	else if ( Q_strlen( pchBinding ) <= 6 )
	{
		Q_strncpy( pchIconTextureName, "icon_key_wide", bufSize );
		return pchBinding;
	}
	else
	{
		Q_strncpy( pchIconTextureName, "icon_key_wide", bufSize );
		return pchBinding;
	}

	return pchBinding;
}

//-----------------------------------------------------------------------------
int CLocatorTarget::GetIconWidth( void )
{
	return m_wide;
}

//-----------------------------------------------------------------------------
int CLocatorTarget::GetIconHeight( void )
{
	return m_tall;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CLocatorPanel : public vgui::EditablePanel
{
	DECLARE_CLASS_SIMPLE( CLocatorPanel, vgui::EditablePanel );
public:
	CLocatorPanel( vgui::Panel *parent, const char *name );
	~CLocatorPanel( void );

	virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
	virtual void PerformLayout( void );
	virtual void OnTick( void );
	virtual void PaintBackground( void );
	virtual void Paint( void );
	void ValidateTexture( int *pTextureID, const char *pszTextureName );
	bool ValidateTargetTextures( CLocatorTarget *pTarget );
	bool IconsAreIntersecting( CLocatorTarget &first, CLocatorTarget &second, int iTolerance );
	virtual void PaintTarget( CLocatorTarget *pTarget );

	void DrawPointerBackground( CLocatorTarget *pTarget, int nPointerX, int nPointerY, int nWide, int nTall, bool bPointer );
	void DrawStaticIcon( CLocatorTarget *pTarget );
	void DrawDynamicIcon( CLocatorTarget *pTarget, bool bDrawCaption, bool bDrawSimpleArrow );
	void DrawIndicatorArrow( int x, int y, int iconWide, int iconTall, int textWidth, int direction );
	void DrawTargetCaption( CLocatorTarget *pTarget, int x, int y, bool bDrawMultiline );
	int  GetScreenWidthForCaption( const wchar_t *pString, vgui::HFont hFont );
	void DrawBindingName( CLocatorTarget *pTarget, const char *pchBindingName, int x, int y, bool bController );
	void ComputeTargetIconPosition( CLocatorTarget *pTarget, bool bSetPosition );
	void CalculateOcclusion( CLocatorTarget *pTarget );

	void DrawSimpleArrow( int x, int y, int iconWide, int iconTall );
	void GetIconPositionForOffscreenTarget( const Vector &vecDelta, float flDist, int *pXPos, int *pYPos );

	CLocatorTarget *GetPointerForHandle( int hTarget );
	int		AddTarget();
	void	RemoveTarget( int hTarget );

	void	GetTargetPosition( const Vector &vecDelta, float flRadius, float *xpos, float *ypos, float *flRotation );

	void	DeactivateAllTargets();
	void	CollectGarbage();

	// Animation
	void	AnimateIconSize( int flags, int *wide, int *tall, float fPulseStart );
	void	AnimateIconPosition( int flags, int *x, int *y );
	void	AnimateIconAlpha( int flags, int *alpha, float fadeStart );

private:

	CPanelAnimationVar( vgui::HFont, m_hCaptionFont, "font", "InstructorTitle" );
	CPanelAnimationVar( vgui::HFont, m_hCaptionFont_ss, "font", "InstructorTitle_ss" );
	CPanelAnimationVar( vgui::HFont, m_hCaptionGlowFont, "font", "InstructorTitleGlow" );
	CPanelAnimationVar( vgui::HFont, m_hCaptionGlowFont_ss, "font", "InstructorTitleGlow_ss" );
	
	CPanelAnimationVar( vgui::HFont, m_hButtonFont, "font", "InstructorButtons" );

	CPanelAnimationVar( vgui::HFont, m_hButtonFont_ss, "font", "InstructorButtons_ss" );
	CPanelAnimationVar( vgui::HFont, m_hKeysFont, "font", "InstructorKeyBindings" );

	
	CPanelAnimationVar( int, m_iShouldWrapStaticLocators, "WrapStaticLocators", "0" );

	static	int		m_serializer;			// Used to issue unique serial numbers to targets, for use as handles
	int				m_textureID_ArrowRight;
	int				m_textureID_ArrowLeft;
	int				m_textureID_ArrowUp;
	int				m_textureID_ArrowDown;
	int				m_textureID_SimpleArrow;

	int				m_staticIconPosition;// Helps us stack static icons

	CLocatorTarget	m_targets[MAX_LOCATOR_TARGETS];
};

//-----------------------------------------------------------------------------
// Local variables
//-----------------------------------------------------------------------------
static CLocatorPanel *s_pLocatorPanel;

inline CLocatorPanel * GetPlayerLocatorPanel()
{
	//if ( !engine->IsLocalPlayerResolvable() )
		//return NULL;

	Assert( s_pLocatorPanel );
	return s_pLocatorPanel;
}


//-----------------------------------------------------------------------------
// Static variable initialization
//-----------------------------------------------------------------------------
int	CLocatorPanel::m_serializer = 1000;		// Serial numbers start at 1000

//-----------------------------------------------------------------------------
// This is the interface function that other systems use to send us targets
//-----------------------------------------------------------------------------
int Locator_AddTarget()
{
	if( s_pLocatorPanel == NULL )
	{
		// Locator has not been used yet. Construct it.
		CLocatorPanel *pLocator = new CLocatorPanel( g_pClientMode->GetViewport(), "LocatorPanel" );
		vgui::SETUP_PANEL(pLocator);
		pLocator->SetBounds( 0, 0, ScreenWidth(), ScreenHeight() );
		pLocator->SetPos( 0, 0 );
		pLocator->SetVisible( true );
		vgui::ivgui()->AddTickSignal( pLocator->GetVPanel() );
	}

	Assert( s_pLocatorPanel != NULL );
	return s_pLocatorPanel ? s_pLocatorPanel->AddTarget() : -1;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void Locator_RemoveTarget( int hTarget )
{
	if ( CLocatorPanel *pPanel = GetPlayerLocatorPanel() )
		pPanel->RemoveTarget( hTarget );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CLocatorTarget *Locator_GetTargetFromHandle( int hTarget )
{
	if ( CLocatorPanel *pPanel = GetPlayerLocatorPanel() )
		return pPanel->GetPointerForHandle( hTarget );
	else
		return NULL;
}

void Locator_ComputeTargetIconPositionFromHandle( int hTarget )
{
	if ( CLocatorPanel *pPanel = GetPlayerLocatorPanel() )
	{
		if ( CLocatorTarget *pTarget = pPanel->GetPointerForHandle( hTarget ) )
		{
			if( !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_STATIC ) )
			{
				// It's not presenting in the middle of the screen, so figure out it's position
				pPanel->ComputeTargetIconPosition( pTarget, !pTarget->IsPresenting() );
				pPanel->CalculateOcclusion( pTarget );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CLocatorPanel::CLocatorPanel( Panel *parent, const char *name ) : EditablePanel(parent,name)
{
	Assert( s_pLocatorPanel == NULL );
	DeactivateAllTargets();

	s_pLocatorPanel				= this;
	m_textureID_ArrowRight		= -1;
	m_textureID_ArrowLeft		= -1;
	m_textureID_ArrowUp			= -1;
	m_textureID_ArrowDown		= -1;
	m_textureID_SimpleArrow		= -1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CLocatorPanel::~CLocatorPanel( void )
{
	Assert( s_pLocatorPanel == this );
	s_pLocatorPanel = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: Applies scheme settings
//-----------------------------------------------------------------------------
void CLocatorPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );
	LoadControlSettings("resource/UI/Locator.res");
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLocatorPanel::PerformLayout( void )
{
	BaseClass::PerformLayout();

	vgui::Panel *pPanel = FindChildByName( "LocatorBG" );

	if ( pPanel )
		pPanel->SetPos( (GetWide() - pPanel->GetWide()) * 0.5, (GetTall() - pPanel->GetTall()) * 0.5 );
}

//-----------------------------------------------------------------------------
// Purpose: Given an offscreen target position, compute the 'compass position'
// so that we can draw an icon on the imaginary circle around the crosshair
// that indicates which way the player should turn to bring the target into view.
//-----------------------------------------------------------------------------
void CLocatorPanel::GetTargetPosition( const Vector &vecDelta, float flRadius, float *xpos, float *ypos, float *flRotation )
{
	// Player Data
	Vector playerPosition	= MainViewOrigin();
	QAngle playerAngles		= MainViewAngles();

	Vector forward, right, up(0,0,1);
	AngleVectors (playerAngles, &forward, NULL, NULL );
	forward.z = 0;
	VectorNormalize(forward);
	CrossProduct( up, forward, right );
	float front = DotProduct(vecDelta, forward);
	float side = DotProduct(vecDelta, right);
	*xpos = flRadius * -side;
	*ypos = flRadius * -front;

	// Get the rotation (yaw)
	*flRotation = atan2(*xpos,*ypos) + M_PI;
	*flRotation *= 180 / M_PI;

	float yawRadians = -(*flRotation) * M_PI / 180.0f;
	float ca = cos( yawRadians );
	float sa = sin( yawRadians );

	// Rotate it around the circle, squash Y to make an oval rather than a circle
	*xpos = (int)((ScreenWidth() / 2) + (flRadius * sa));
	*ypos = (int)((ScreenHeight() / 2) - (flRadius * 0.6f * ca));
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CLocatorPanel::DeactivateAllTargets()
{
	for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ )
		m_targets[ i ].Deactivate( true );
}

//-----------------------------------------------------------------------------
// Purpose: Deactivate any target that has not been updated within several frames
//-----------------------------------------------------------------------------
void CLocatorPanel::CollectGarbage()
{
	for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ )
	{
		if( m_targets[ i ].m_isActive )
		{
			if( gpGlobals->framecount - m_targets[ i ].m_frameLastUpdated > 20 )
				m_targets[ i ].Deactivate();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Provide simple animation by modifying the width and height of the
// icon before it is drawn.
//-----------------------------------------------------------------------------
void CLocatorPanel::AnimateIconSize( int flags, int *wide, int *tall, float fPulseStart )
{
	float flScale	 = MIN_ICON_SCALE;
	float scaleDelta = MAX_ICON_SCALE - MIN_ICON_SCALE;

	float newWide = *wide;
	float newTall = *tall;

	if( flags & LOCATOR_ICON_FX_PULSE_SLOW || gpGlobals->curtime - fPulseStart < locator_pulse_time.GetFloat() )
	{
		flScale += scaleDelta * fabs( sin( ( gpGlobals->curtime - fPulseStart ) * M_PI ) );
	}
	else if( flags & LOCATOR_ICON_FX_PULSE_FAST )
	{
		flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 2 * M_PI ) );
	}
	else if( flags & LOCATOR_ICON_FX_PULSE_URGENT )
	{
		flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 4 * M_PI ) );
	}

	if ( newWide > newTall )
	{
		// Get scale to make width change by only the standard height amount of pixels
		int iHeightDelta = (int)(newTall * flScale - newTall);
		flScale = ( newWide + iHeightDelta ) / newWide;
	}

	newWide = newWide * flScale;
	newTall = newTall * flScale;

	*wide = newWide;
	*tall = newTall;
}

//-----------------------------------------------------------------------------
// Purpose: Modify the alpha of the icon before it is drawn.
//-----------------------------------------------------------------------------
void CLocatorPanel::AnimateIconAlpha( int flags, int *alpha, float fadeStart )
{
	float flScale = MIN_ICON_ALPHA;
	float scaleDelta = MAX_ICON_ALPHA - MIN_ICON_ALPHA;

	if( flags & LOCATOR_ICON_FX_ALPHA_SLOW )
	{
		flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 3 ) );
	}
	else if( flags & LOCATOR_ICON_FX_ALPHA_FAST )
	{
		flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 7 ) );
	}
	else if( flags & LOCATOR_ICON_FX_ALPHA_URGENT )
	{
		flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 10 ) );
	}
	else
	{
		flScale = MAX_ICON_ALPHA;
	}

	if ( flags & LOCATOR_ICON_FX_FADE_OUT )
	{
		flScale *= MAX( 0.0f, ( locator_fade_time.GetFloat() - ( gpGlobals->curtime - fadeStart ) ) / locator_fade_time.GetFloat() );
	}
	else if ( flags & LOCATOR_ICON_FX_FADE_IN )
	{
		flScale *= MAX_ICON_ALPHA - MAX( 0.0f, ( locator_fade_time.GetFloat() - ( gpGlobals->curtime - fadeStart ) ) / locator_fade_time.GetFloat() );
	}

	*alpha = static_cast<int>( 255.0f * flScale );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLocatorPanel::AnimateIconPosition( int flags, int *x, int *y )
{
	int newX = *x;
	int newY = *y;

	if( flags & LOCATOR_ICON_FX_SHAKE_NARROW )
	{
		newX += RandomInt( -2, 2 );
		newY += RandomInt( -2, 2 );
	}
	else if( flags & LOCATOR_ICON_FX_SHAKE_WIDE )
	{
		newX += RandomInt( -5, 5 );
		newY += RandomInt( -5, 5 );
	}

	*x = newX;
	*y = newY;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLocatorPanel::OnTick( void )
{

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLocatorPanel::PaintBackground( void )
{
	return;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLocatorPanel::Paint( void )
{
	ValidateTexture( &m_textureID_ArrowLeft, "vgui/hud/icon_arrow_left" );
	ValidateTexture( &m_textureID_ArrowRight, "vgui/hud/icon_arrow_right" );
	ValidateTexture( &m_textureID_ArrowUp, "vgui/hud/icon_arrow_up" );
	ValidateTexture( &m_textureID_ArrowDown, "vgui/hud/icon_arrow_down" );
	ValidateTexture( &m_textureID_SimpleArrow, "vgui/hud/icon_arrow_plain" );

	// reset the static icon position. This is the y position at which the first
	// static icon will be drawn. This value will be incremented by the height of
	// each static icon drawn, which forces the next static icon to be drawn below
	// the previous one, creating a little fixed, vertical stack below the crosshair.
	m_staticIconPosition = ScreenHeight() / 6;

	// Time now to draw the 'dynamic' icons, the icons which help players locate things
	// in actual world space.

	//----------
	// Batch 1
	// Go through all of the active locator targets and compute where to draw the icons
	// that represent each of them. This builds a poor man's draw list by updating the 
	// m_iconX, m_iconY members of each locator target.
	CUtlVectorFixed< CLocatorTarget *, MAX_LOCATOR_TARGETS > vecValid;

	for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ )
	{
		CLocatorTarget *pLocatorTarget = &(m_targets[ i ]);

		// Reset drawing state for this frame... set back to true when it's finally draws
		pLocatorTarget->m_bIsDrawing = false;

		if ( ( !pLocatorTarget->m_bVisible && !pLocatorTarget->m_alpha ) || !pLocatorTarget->m_isActive )
		{
			// Don't want to be visible and have finished fading
			continue;
		}

		vecValid.AddToTail( pLocatorTarget );

		// This prevents an error that if a locator was fading as the map transitioned
		pLocatorTarget->m_fadeStart = fpmin( pLocatorTarget->m_fadeStart, gpGlobals->curtime ); 

		if( !( pLocatorTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_STATIC ) )
		{
			// It's not presenting in the middle of the screen, so figure out it's position
			ComputeTargetIconPosition( pLocatorTarget, !pLocatorTarget->IsPresenting() );
			CalculateOcclusion( pLocatorTarget );

			pLocatorTarget->m_lastDeclutterIndex = pLocatorTarget->m_declutterIndex;
			pLocatorTarget->m_declutterIndex = 0;
		}
	}

	//----------
	// Batch 2
	// Now that we know where each icon _wants_ to be drawn, we grovel through them and 
	// push apart any icons that are too close to one another. This helps to unclutter
	// the display and ensure the maximum number of legible icons and captions. Obviously
	// this process changes where some icons will be drawn. Bubble sort, but tiny data set.
	int iTolerance = 1.25 * (ScreenWidth() * ICON_SIZE);
	int iterations = 0;// Count iterations, don't go infinite in the event of some weird case.
	bool bStillUncluttering = true;
	static int MAX_UNCLUTTER_ITERATIONS = 10;
	while( iterations < MAX_UNCLUTTER_ITERATIONS && bStillUncluttering )
	{
		iterations++;
		bStillUncluttering = false;

		for( int i = 0 ; i < vecValid.Count() ; ++i )
		{
			CLocatorTarget *pLocatorTarget1 = vecValid[ i ];

			for( int j = i + 1 ; j < vecValid.Count() ; ++j )
			{
				CLocatorTarget *pLocatorTarget2 = vecValid[ j ];

				// Don't attempt to declutter icons if one or both is attempting to fade out
				bool bLocatorsFullyActive = !((pLocatorTarget1->GetIconEffectsFlags()|pLocatorTarget2->GetIconEffectsFlags()) & LOCATOR_ICON_FX_FADE_OUT);

				if ( bLocatorsFullyActive && IconsAreIntersecting( *pLocatorTarget1, *pLocatorTarget2, iTolerance ) )
				{
					// Unclutter. Lift whichever icon is highest a bit higher
					if( pLocatorTarget1->m_iconY < pLocatorTarget2->m_iconY )
					{
						pLocatorTarget1->m_iconY = pLocatorTarget2->m_iconY - iTolerance;
						pLocatorTarget1->m_centerY = pLocatorTarget2->m_centerY - iTolerance;
						pLocatorTarget1->m_declutterIndex -= 1;
					}
					else
					{
						pLocatorTarget2->m_iconY = pLocatorTarget1->m_iconY - iTolerance;
						pLocatorTarget2->m_centerY = pLocatorTarget1->m_centerY - iTolerance;
						pLocatorTarget2->m_declutterIndex -= 1;
					}

					bStillUncluttering = true;
				}
			}
		}
	}

	if( iterations == MAX_UNCLUTTER_ITERATIONS )
	{
		DevWarning( "Game instructor hit MAX_UNCLUTTER_ITERATIONS!\n");
	}

	float flLocatorLerpRest = locator_lerp_rest.GetFloat();
	float flLocatorLerpTime = locator_lerp_time.GetFloat();

	//----------
	// Batch 3
	// Draw each of the icons.
	for( int i = 0 ; i < vecValid.Count() ; i++ )
	{
		CLocatorTarget *pLocatorTarget = vecValid[ i ];
		// Back to lerping for these guys
		if ( pLocatorTarget->m_lastDeclutterIndex != pLocatorTarget->m_declutterIndex )
		{
			// It wants to be popped to another position... do it smoothly
			pLocatorTarget->StartTimedLerp();
		}

		// Lerp to the desired position
		float flLerpTime = gpGlobals->curtime - pLocatorTarget->m_lerpStart;

		if ( flLerpTime >= flLocatorLerpRest && flLerpTime < flLocatorLerpRest + flLocatorLerpTime )
		{
			// Lerp slow to fast
			float fInterp = 1.0f - ( ( flLocatorLerpTime - ( flLerpTime - flLocatorLerpRest ) ) / flLocatorLerpTime );

			// Get our desired position
			float iconX = pLocatorTarget->m_iconX;
			float iconY = pLocatorTarget->m_iconY;

			// Get the distance we need to go to reach it
			float diffX = fabsf( pLocatorTarget->m_iconX - pLocatorTarget->m_lastXPos );
			float diffY = fabsf( pLocatorTarget->m_iconY - pLocatorTarget->m_lastYPos );

			// Go from our current position toward the desired position as quick as the interp allows
			pLocatorTarget->m_iconX = static_cast<int>( Approach( iconX, pLocatorTarget->m_lastXPos, diffX * fInterp ) );
			pLocatorTarget->m_iconY = static_cast<int>( Approach( iconY, pLocatorTarget->m_lastYPos, diffY * fInterp ) );

			// Get how much our position changed and apply it to the center values
			int iOffsetX = pLocatorTarget->m_iconX - iconX;
			int iOffsetY = pLocatorTarget->m_iconY - iconY;

			pLocatorTarget->m_centerX += iOffsetX;
			pLocatorTarget->m_centerY += iOffsetY;

			if ( iOffsetX < 3 && iOffsetY < 3 )
			{
				// Near our target! Stop lerping!
				flLerpTime = flLocatorLerpRest + flLocatorLerpTime;
			}
		}

		PaintTarget( pLocatorTarget );
	}

	CollectGarbage();
}

//-----------------------------------------------------------------------------
// Purpose: A helper function to save on typing. Make sure our texture ID's 
//			stay valid.
//-----------------------------------------------------------------------------
void CLocatorPanel::ValidateTexture( int *pTextureID, const char *pszTextureName )
{
	if( *pTextureID == -1 )
	{
		*pTextureID = vgui::surface()->CreateNewTextureID();
		vgui::surface()->DrawSetTextureFile( *pTextureID, pszTextureName, true, false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called every frame before painting the targets. Ensures that the
//			target's textures are properly cached.
//-----------------------------------------------------------------------------
bool CLocatorPanel::ValidateTargetTextures( CLocatorTarget *pTarget )
{
	bool bBindingTick = false;

	if ( gpGlobals->curtime >= pTarget->m_flNextBindingTick )
	{
		if ( pTarget->m_iBindingChoicesCount > 1 )
		{
			bBindingTick = true;
			pTarget->m_iBindingTick++;
		}

		pTarget->m_flNextBindingTick = gpGlobals->curtime + 0.75f;

		pTarget->UpdateVguiTarget();
	}

	bool bUsesBinding = ( Q_stricmp( pTarget->GetOnscreenIconTextureName(), "use_binding" ) == 0 );

	if( !pTarget->m_pIcon_onscreen || !pTarget->m_pIcon_offscreen || ( bUsesBinding && bBindingTick ) )
	{
		char szIconTextureName[ 256 ];
		if ( bUsesBinding )
		{
			const char *pchDrawBindingName		= pTarget->UseBindingImage( szIconTextureName, sizeof( szIconTextureName ) );
			pTarget->m_bDrawControllerButton		= ( Q_strcmp( szIconTextureName, "icon_blank" ) == 0 );

			pTarget->DrawBindingName( pchDrawBindingName );
		}
		else
		{
			pTarget->m_bDrawControllerButton = false;
			Q_strcpy( szIconTextureName, pTarget->GetOnscreenIconTextureName() );
			pTarget->DrawBindingName( NULL );
		}

		// This target's texture ID is dirty, meaning the target is about to be drawn
		// for the first time, or about to be drawn for the first time since a texture
		// was changed.
		if ( Q_strlen(szIconTextureName) == 0 )
		{
			DevWarning("Locator Target has no onscreen texture name!\n");
			return false;
		}
		else
		{
			pTarget->m_pIcon_onscreen = HudIcons().GetIcon( szIconTextureName );
			if ( pTarget->m_pIcon_onscreen )
			{
				pTarget->m_widthScale_onscreen = static_cast< float >( pTarget->m_pIcon_onscreen->Width() ) / pTarget->m_pIcon_onscreen->Height();
			}
			else
			{
				pTarget->m_widthScale_onscreen = 1.0f;
			}
		}

		if ( Q_stricmp( pTarget->GetOffscreenIconTextureName() , "use_binding" ) == 0 )
		{
			const char *pchDrawBindingName = pTarget->UseBindingImage( szIconTextureName, sizeof( szIconTextureName ) );
			pTarget->m_bDrawControllerButtonOffscreen = ( Q_strcmp( szIconTextureName, "icon_blank" ) == 0 );

			pTarget->DrawBindingNameOffscreen( pchDrawBindingName );
		}
		else
		{
			pTarget->m_bDrawControllerButtonOffscreen = false;
			Q_strcpy( szIconTextureName, pTarget->GetOffscreenIconTextureName() );
			pTarget->DrawBindingNameOffscreen( NULL );
		}

		if( Q_strlen(szIconTextureName) == 0 )
		{
			if( !pTarget->m_pIcon_onscreen )
			{
				DevWarning("Locator Target has no offscreen texture name and can't fall back!\n");
			}
			else
			{
				// The onscreen texture is valid, so default behavior is to use that.
				pTarget->m_pIcon_offscreen = pTarget->m_pIcon_onscreen;
				const char *pchDrawBindingName = pTarget->DrawBindingName();
				pTarget->DrawBindingNameOffscreen( pchDrawBindingName );
			}
		}
		else
		{
			pTarget->m_pIcon_offscreen = HudIcons().GetIcon( szIconTextureName );
		}

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Compute where on the screen to draw the icon for this target.
//-----------------------------------------------------------------------------
void CLocatorPanel::ComputeTargetIconPosition( CLocatorTarget *pTarget, bool bSetPosition )
{
	int iconX;
	int iconY;

	// Measure the delta and the dist from this player to this target.
	Vector vecTarget = pTarget->m_vecOrigin;
	Vector vecDelta = vecTarget - MainViewOrigin();

	if ( pTarget->m_bOriginInScreenspace )
	{
		// Coordinates are already in screenspace
		pTarget->m_distFromPlayer = 0.0f;

		iconX = vecTarget.x * ScreenWidth();
		iconY = vecTarget.y * ScreenHeight();
		pTarget->m_targetX = iconX;
		pTarget->m_targetY = iconY;
	}
	else
	{
		pTarget->m_distFromPlayer = VectorNormalize( vecDelta );

		if ( GetVectorInScreenSpace( vecTarget, iconX, iconY ) )
		{
			// NOTE: GetVectorInScreenSpace returns false in an edge case where the 
			// target is very far off screen... just us the old values
			pTarget->m_targetX = iconX;
			pTarget->m_targetY = iconY;
		}
	}

	pTarget->m_drawArrowDirection = DRAW_ARROW_NO;

	float fTitleSafeInset = ScreenWidth() * 0.075f;

	if( iconX < fTitleSafeInset || iconX > ScreenWidth() - fTitleSafeInset )
	{
		// It's off the screen left or right.
		if ( pTarget->m_bOnscreen && !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) )
		{
			// Back to lerping
			pTarget->StartTimedLerp();
			pTarget->m_pulseStart = gpGlobals->curtime;
		}

		if ( bSetPosition )
		{
			pTarget->m_bOnscreen = false;
		}

		GetIconPositionForOffscreenTarget( vecDelta, pTarget->m_distFromPlayer, &iconX, &iconY );

		Vector vCenter = pTarget->m_vecOrigin;
		if( MainViewRight().Dot( vCenter - MainViewOrigin() ) > 0 )
			pTarget->m_drawArrowDirection = DRAW_ARROW_RIGHT;
		else
			pTarget->m_drawArrowDirection = DRAW_ARROW_LEFT;
	}
	else if( iconY < fTitleSafeInset || iconY > ScreenHeight() - fTitleSafeInset )
	{
		// It's off the screen up or down.
		if ( pTarget->m_bOnscreen && !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) )
		{
			// Back to lerping
			pTarget->StartTimedLerp();
			pTarget->m_pulseStart = gpGlobals->curtime;
		}

		if ( bSetPosition )
		{
			pTarget->m_bOnscreen = false;
		}

		GetIconPositionForOffscreenTarget( vecDelta, pTarget->m_distFromPlayer, &iconX, &iconY );

		Vector vCenter = pTarget->m_vecOrigin;
		if( MainViewUp().Dot( vCenter - MainViewOrigin() ) > 0 )
			pTarget->m_drawArrowDirection = DRAW_ARROW_UP;
		else
			pTarget->m_drawArrowDirection = DRAW_ARROW_DOWN;
	}
	else
	{
		if ( !pTarget->m_bOnscreen && !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) )
		{
			// Back to lerping
			pTarget->StartTimedLerp();
			pTarget->m_pulseStart = gpGlobals->curtime;
		}

		pTarget->m_bOnscreen = true;
	}

	if ( bSetPosition )
	{
		int tall = ScreenWidth() * ICON_SIZE;
		int wide = tall * pTarget->m_widthScale_onscreen;

		// Animate the icon
		AnimateIconSize( pTarget->GetIconEffectsFlags(), &wide, &tall, pTarget->m_pulseStart );
		AnimateIconPosition( pTarget->GetIconEffectsFlags(), &iconX, &iconY );
		AnimateIconAlpha( pTarget->GetIconEffectsFlags(), &pTarget->m_alpha, pTarget->m_fadeStart );

		if( pTarget->m_distFromPlayer > ICON_DIST_TOO_FAR && !locator_topdown_style.GetBool() )
		{
			// Make the icon smaller
			wide = wide >> 1;
			tall = tall >> 1;
		}

		pTarget->m_centerX = iconX;
		pTarget->m_centerY = iconY;

		pTarget->m_iconX = pTarget->m_centerX - ( wide >> 1 );
		pTarget->m_iconY = pTarget->m_centerY - ( tall >> 1 );
		pTarget->m_wide = wide;
		pTarget->m_tall = tall;
	}
}

void CLocatorPanel::CalculateOcclusion( CLocatorTarget *pTarget )
{
	if ( gpGlobals->curtime >= pTarget->m_flNextOcclusionTest )
	{
		pTarget->m_flNextOcclusionTest = gpGlobals->curtime + LOCATOR_OCCLUSION_TEST_RATE;

		// Assume the target is not occluded.
		pTarget->m_bOccluded = false;

		if ( pTarget->m_bOriginInScreenspace )
			return;

		trace_t	tr;
		UTIL_TraceLine( pTarget->m_vecOrigin, MainViewOrigin(), (CONTENTS_SOLID|CONTENTS_MOVEABLE), NULL, COLLISION_GROUP_NONE, &tr );
		if ( tr.fraction < 1.0f )
		{
			pTarget->m_bOccluded = true;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: This is not valid until after you have computed the onscreen
//			icon position for each target!
//-----------------------------------------------------------------------------
bool CLocatorPanel::IconsAreIntersecting( CLocatorTarget &first, CLocatorTarget &second, int iTolerance )
{
	if( first.m_bOnscreen != second.m_bOnscreen )
	{
		// We only declutter onscreen icons against other onscreen icons and vice-versa.
		return false;
	}

	if( first.IsStatic() || second.IsStatic() )
	{
		// Static icons don't count.
		return false;
	}

	if( abs(first.GetIconY() - second.GetIconY()) < iTolerance )
	{
		// OK, we need the Y-check first. Now we have to see if these icons and their captions overlap.
		int firstWide = iTolerance + first.m_captionWide;
		int secondWide = iTolerance + second.m_captionWide;
		
		if( abs(first.GetIconX() - second.GetIconX()) < (firstWide + secondWide) / 2 )
		{
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Draw this target on the locator.
//
// IF onscreen and visible, draw no icon, draw no arrows
// IF onscreen and occluded, draw icon transparently, draw no arrows
// IF offscreen, draw icon, draw an arrow indicating the direction to the target
//-----------------------------------------------------------------------------
void CLocatorPanel::PaintTarget( CLocatorTarget *pTarget )
{
	bool bNewTexture = ValidateTargetTextures( pTarget );

	if ( bNewTexture )
	{
		// Refigure the width/height for the new texture
		int tall = ScreenWidth() * ICON_SIZE;
		int wide = tall * pTarget->m_widthScale_onscreen;

		AnimateIconSize( pTarget->GetIconEffectsFlags(), &wide, &tall, pTarget->m_pulseStart );

		pTarget->m_wide = wide;
		pTarget->m_tall = tall;
	}

	// A static icon just draws with other static icons in a stack under the crosshair. 
	// Once displayed, they do not move. The are often used for notifiers.
	if( pTarget->IsStatic() )
	{
		DrawStaticIcon( pTarget );
		return;
	}

	if ( !pTarget->m_bOnscreen && ( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) )
	{
		// Doesn't draw when offscreen... reset it's alpha so it has to fade in again
		pTarget->m_fadeStart = gpGlobals->curtime;
		pTarget->m_alpha = 0;
	}
	else
	{
		// Save these coordinates for later lerping
		pTarget->m_lastXPos = pTarget->m_iconX;
		pTarget->m_lastYPos = pTarget->m_iconY;

		// Draw when it's on screen or allowed to draw offscreen
		DrawDynamicIcon( pTarget, pTarget->HasCaptionText(), pTarget->m_bOnscreen );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Draws the caption-like background with word-bubble style pointer
//-----------------------------------------------------------------------------
void CLocatorPanel::DrawPointerBackground( CLocatorTarget *pTarget, int nPointerX, int nPointerY, int nWide, int nTall, bool bPointer )
{
	if ( locator_background_style.GetInt() == 0 || pTarget->m_alpha == 0 )
		return;

	int nPosX = pTarget->GetIconX() + locator_background_shift_x.GetInt() - locator_background_thickness_x.GetInt() / 2;
	int nPosY = pTarget->GetIconY() + locator_background_shift_y.GetInt() - locator_background_thickness_y.GetInt() / 2;
	int nBackgroundWide = nWide + locator_background_thickness_x.GetInt();
	int nBackgroundTall = nTall + locator_background_thickness_y.GetInt();

	nPointerX = clamp( nPointerX, -0.5f * ScreenWidth(), ScreenWidth() * 1.5f );
	nPointerY = clamp( nPointerY, -0.5f * ScreenHeight(), ScreenHeight() * 1.5f );

	float fAlpha = static_cast<float>( pTarget->m_alpha ) / 255.0f;

	Color rgbaBackground = locator_background_color.GetColor();
	rgbaBackground[ 3 ] *= fAlpha;

	Color rgbaBorder = locator_background_border_color.GetColor();
	rgbaBorder[ 3 ] *= fAlpha;

	DevMsg("[TODO] vgui::surface()->DrawWordBubble \n");

	//vgui::surface()->DrawWordBubble( nPosX, nPosY, nPosX + nBackgroundWide, nPosY + nBackgroundTall, locator_background_border_thickness.GetInt(),  rgbaBackground, rgbaBorder, bPointer, nPointerX, nPointerY, ScreenWidth() * ICON_SIZE );
}

//-----------------------------------------------------------------------------
// Purpose: Draw an icon with the group of static icons. 
//-----------------------------------------------------------------------------
void CLocatorPanel::DrawStaticIcon( CLocatorTarget *pTarget )
{
	int centerX = ScreenWidth() / 2;
	int centerY = ScreenHeight() / 2;
	centerY += m_staticIconPosition;

	int iconTall = ScreenWidth() * ICON_SIZE;
	int iconWide = iconTall * pTarget->m_widthScale_onscreen;

	pTarget->m_centerX = centerX;
	pTarget->m_centerY = centerY;

	// Animate the icon
	AnimateIconSize( pTarget->GetIconEffectsFlags(), &iconWide, &iconTall, pTarget->m_pulseStart );
	AnimateIconPosition( pTarget->GetIconEffectsFlags(), &centerX, &centerY );
	AnimateIconAlpha( pTarget->GetIconEffectsFlags(), &pTarget->m_alpha, pTarget->m_fadeStart );

	// Figure out the caption width
	pTarget->m_captionWide = GetScreenWidthForCaption( pTarget->GetCaptionText(), m_hCaptionFont );

	bool bDrawMultilineCaption = false;

	if ( m_iShouldWrapStaticLocators > 0 )	// conditionalized in locator.res
	{
		if ( pTarget->m_captionWide > (  ScreenWidth() * locator_split_maxwide_percent.GetFloat() ) )
		{
			// we will double-line this
			pTarget->m_captionWide = pTarget->m_captionWide * locator_split_len.GetFloat();
			bDrawMultilineCaption = true;
		}
	}
	int totalWide = iconWide + ICON_GAP + pTarget->m_captionWide;
	pTarget->m_iconX = centerX - totalWide * 0.5f;
	pTarget->m_iconY = centerY - ( iconTall >> 1 );

	// Lerp by speed on the Y axis
	float iconY = pTarget->m_iconY;

	float diffY = fabsf( pTarget->m_iconY - pTarget->m_lastYPos );

	float flLerpSpeed = gpGlobals->frametime * locator_lerp_speed.GetFloat();
	pTarget->m_iconY = static_cast<int>( Approach( iconY, pTarget->m_lastYPos, MAX( 3.0f, flLerpSpeed * diffY ) ) );
	pTarget->m_centerY += ( pTarget->m_iconY - iconY );

	pTarget->m_lastXPos = pTarget->m_iconX;
	pTarget->m_lastYPos = pTarget->m_iconY;

	pTarget->m_bIsDrawing = true;

	vgui::Panel *pVguiTarget = pTarget->GetVguiTarget();

	if ( pVguiTarget )
	{
		int nPanelX, nPanelY;
		nPanelX = 0;
		nPanelY = 0;

		vgui::Label::Alignment nVguiTargetEdge = (vgui::Label::Alignment)pTarget->GetVguiTargetEdge();

		int nWide = pVguiTarget->GetWide();
		int nTall = pVguiTarget->GetTall();

		const char *pchLookup = pTarget->GetVguiTargetLookup();
		/*if ( pchLookup[ 0 ] != '\0' )
		{
			bool bLookupSuccess = false;
			bLookupSuccess = pVguiTarget->LookupElementBounds( pchLookup, nPanelX, nPanelY, nWide, nTall );

			Assert( bLookupSuccess );
		}*/

		if ( nVguiTargetEdge == vgui::Label::a_north || 
			 nVguiTargetEdge == vgui::Label::a_center || 
			 nVguiTargetEdge == vgui::Label::a_south )
		{
			nPanelX += nWide / 2;
		}
		else if ( nVguiTargetEdge == vgui::Label::a_northeast || 
			 nVguiTargetEdge == vgui::Label::a_east || 
			 nVguiTargetEdge == vgui::Label::a_southeast )
		{
			nPanelX += nWide;
		}

		if ( nVguiTargetEdge == vgui::Label::a_west || 
			 nVguiTargetEdge == vgui::Label::a_center || 
			 nVguiTargetEdge == vgui::Label::a_east )
		{
			nPanelY += nTall / 2;
		}
		else if ( nVguiTargetEdge == vgui::Label::a_southwest || 
			 nVguiTargetEdge == vgui::Label::a_south || 
			 nVguiTargetEdge == vgui::Label::a_southeast )
		{
			nPanelY += nTall;
		}

		pVguiTarget->LocalToScreen( nPanelX, nPanelY );

		DrawPointerBackground( pTarget, nPanelX, nPanelY, totalWide, iconTall, true );
	}
	else
	{
		DrawPointerBackground( pTarget, pTarget->m_centerX, pTarget->m_centerY, totalWide, iconTall, false );
	}

	if ( pTarget->m_pIcon_onscreen )
	{
		if ( !pTarget->m_bDrawControllerButton )
		{
			// Don't draw the icon if we're on 360 and have a binding to draw
			pTarget->m_pIcon_onscreen->DrawSelf( pTarget->GetIconX(), pTarget->GetIconY(), iconWide, iconTall, Color( 255, 255, 255, pTarget->m_alpha ) );
		}
	}

	DrawTargetCaption( pTarget, pTarget->GetIconX() + iconWide + ICON_GAP, pTarget->GetIconCenterY(), bDrawMultilineCaption );
	if ( pTarget->DrawBindingName() )
	{
		DrawBindingName( pTarget, pTarget->DrawBindingName(), pTarget->GetIconX() + (iconWide>>1), pTarget->GetIconY() + (iconTall>>1), pTarget->m_bDrawControllerButton );
	}

	// Draw the arrow.
	int iArrowSize = ScreenWidth() * ICON_SIZE;	// Always square width
	DrawIndicatorArrow( pTarget->GetIconX(), pTarget->GetIconY(), iArrowSize, iArrowSize, pTarget->m_captionWide + ICON_GAP, pTarget->m_drawArrowDirection );

	pTarget->m_bOnscreen = true;

	// Move the static icon position so the next static icon drawn this frame below this one.
	m_staticIconPosition += iconTall + (iconTall>>2);
	// Move down a little more if this one was multi-line
	if ( bDrawMultilineCaption )
	{
		m_staticIconPosition += (iconTall>>2);
	}
	return;
}

//-----------------------------------------------------------------------------
// Purpose: Position and animate this target's icon on the screen. Based on
//			options, draw the indicator arrows (arrows that point to the 
//			direction the player should turn to see the icon), text caption, 
//			and the 'simple' arrow which just points down to indicate the 
//			item the icon represents.
//-----------------------------------------------------------------------------
void CLocatorPanel::DrawDynamicIcon( CLocatorTarget *pTarget, bool bDrawCaption, bool bDrawSimpleArrow )
{
	int alpha = pTarget->m_alpha;

	if( pTarget->m_bOccluded && !( (pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_FORCE_CAPTION) || locator_topdown_style.GetBool() ) )
	{
		return;
	}

	// Draw the icon!
	vgui::surface()->DrawSetColor( 255, 255, 255, alpha );

	int iWide = pTarget->m_wide;

	if ( !pTarget->m_bOnscreen )
	{
		// Width is always square for offscreen icons
		iWide /= pTarget->m_widthScale_onscreen;
	}

	// Figure out the caption width
	pTarget->m_captionWide = GetScreenWidthForCaption( pTarget->GetCaptionText(), m_hCaptionFont );
	
	bool bDrawMultilineCaption = false;

	if ( m_iShouldWrapStaticLocators > 0 )	// conditionalized in locator.res
	{
		if ( pTarget->m_captionWide > (  ScreenWidth() * locator_split_maxwide_percent.GetFloat() ) )
		{
			// we will double-line this
			pTarget->m_captionWide = pTarget->m_captionWide * locator_split_len.GetFloat();
			bDrawMultilineCaption = true;
		}
	}

	int totalWide = iWide;

	bool bShouldDrawCaption = ( (pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_FORCE_CAPTION) || (!pTarget->m_bOccluded && pTarget->m_distFromPlayer <= ICON_DIST_TOO_FAR) || locator_topdown_style.GetBool() );

	if( pTarget->m_bOnscreen && bDrawCaption && bShouldDrawCaption )
	{
		totalWide += ( ICON_GAP + pTarget->m_captionWide );
	}

	pTarget->m_bIsDrawing = true;

	int nTargetX, nTargetY;

	vgui::Panel *pVguiTarget = pTarget->GetVguiTarget();

	if ( pVguiTarget )
	{
		nTargetX = 0;
		nTargetY = 0;

		vgui::Label::Alignment nVguiTargetEdge = (vgui::Label::Alignment)pTarget->GetVguiTargetEdge();

		int nWide = pVguiTarget->GetWide();
		int nTall = pVguiTarget->GetTall();

		const char *pchLookup = pTarget->GetVguiTargetLookup();
		
		/*if ( pchLookup[ 0 ] != '\0' )
		{
			bool bLookupSuccess = false;
			bLookupSuccess = pVguiTarget->LookupElementBounds( pchLookup, nTargetX, nTargetY, nWide, nTall );
			Assert( bLookupSuccess );
		}*/

		if ( nVguiTargetEdge == vgui::Label::a_north || 
			 nVguiTargetEdge == vgui::Label::a_center || 
			 nVguiTargetEdge == vgui::Label::a_south )
		{
			nTargetX += nWide / 2;
		}
		else if ( nVguiTargetEdge== vgui::Label::a_northeast || 
			 nVguiTargetEdge == vgui::Label::a_east || 
			 nVguiTargetEdge == vgui::Label::a_southeast )
		{
			nTargetX += nWide;
		}

		if ( nVguiTargetEdge == vgui::Label::a_west || 
			 nVguiTargetEdge == vgui::Label::a_center || 
			 nVguiTargetEdge == vgui::Label::a_east )
		{
			nTargetY += nTall / 2;
		}
		else if ( nVguiTargetEdge == vgui::Label::a_southwest || 
			 nVguiTargetEdge == vgui::Label::a_south || 
			 nVguiTargetEdge == vgui::Label::a_southeast )
		{
			nTargetY += nTall;
		}

		pVguiTarget->LocalToScreen( nTargetX, nTargetY );
	}
	else if ( !pTarget->m_bOnscreen )
	{
		nTargetX = pTarget->m_targetX;
		nTargetY = pTarget->m_targetY;
	}
	else
	{
		nTargetX = pTarget->m_centerX;
		nTargetY = pTarget->m_centerY;
	}

	if ( pTarget->m_bOnscreen )
	{
		DrawPointerBackground( pTarget, nTargetX, nTargetY, totalWide, pTarget->m_tall, true );
	}
	else
	{
		// Offscreen we need to point the pointer toward out offscreen target
		DrawPointerBackground( pTarget, nTargetX, nTargetY, totalWide, pTarget->m_tall, true );
	}

	if( pTarget->m_bOnscreen && pTarget->m_pIcon_onscreen )
	{
		if ( !pTarget->m_bDrawControllerButton )
		{
			pTarget->m_pIcon_onscreen->DrawSelf( pTarget->GetIconX(), pTarget->GetIconY(), iWide, pTarget->m_tall, Color( 255, 255, 255, alpha ) );
		}
	}
	else if ( pTarget->m_pIcon_offscreen )
	{
		if ( !pTarget->m_bDrawControllerButtonOffscreen )
		{
			pTarget->m_pIcon_offscreen->DrawSelf( pTarget->GetIconX(), pTarget->GetIconY(), iWide, pTarget->m_tall, Color( 255, 255, 255, alpha ) );
		}
	}

	if( !pTarget->m_bOnscreen )
	{
		if ( pTarget->DrawBindingNameOffscreen() )
		{
			DrawBindingName( pTarget, pTarget->DrawBindingName(), pTarget->GetIconX() + (iWide>>1), pTarget->GetIconY() + (pTarget->m_tall>>1), pTarget->m_bDrawControllerButtonOffscreen );
		}

		if ( locator_background_style.GetInt() == 0 )
		{
			// Draw the arrow.
			DrawIndicatorArrow( pTarget->GetIconX(), pTarget->GetIconY(), iWide, pTarget->m_tall, 0, pTarget->m_drawArrowDirection );
		}
	}
	else if( bShouldDrawCaption )
	{
		if( bDrawCaption )
		{
			//ScreenWidth() *  * pTarget->m_widthScale_onscreen
			DrawTargetCaption( pTarget, pTarget->GetIconCenterX() + ICON_GAP + pTarget->GetIconWidth() * 0.5, pTarget->GetIconCenterY(), bDrawMultilineCaption );
		}
		if ( pTarget->DrawBindingName() )
		{
			DrawBindingName( pTarget, pTarget->DrawBindingName(), pTarget->GetIconX() + (iWide>>1), pTarget->GetIconY() + (pTarget->m_tall>>1), pTarget->m_bDrawControllerButton );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Some targets have text captions. Draw the text.
//-----------------------------------------------------------------------------
void CLocatorPanel::DrawTargetCaption( CLocatorTarget *pTarget, int x, int y, bool bDrawMultiline )
{
	// Draw the caption
	vgui::surface()->DrawSetTextFont( m_hCaptionFont );
	int fontTall = vgui::surface()->GetFontTall( m_hCaptionFont );
	int iCaptionWidth = GetScreenWidthForCaption( pTarget->GetCaptionText(), m_hCaptionFont );

	if ( bDrawMultiline )
	{
		iCaptionWidth *= locator_split_len.GetFloat();
	}

	if ( locator_text_glow.GetBool() )
	{
		vgui::surface()->DrawSetTextFont( m_hCaptionGlowFont );
		Color glowColor = locator_text_glow_color.GetColor();
		vgui::surface()->DrawSetTextColor( glowColor.r(), glowColor.g(), glowColor.b(), ( glowColor.a() / 255.0f ) * pTarget->m_alpha );
		vgui::surface()->DrawSetTextPos( x - 1, y - (fontTall >>1) - 1 );
		vgui::surface()->DrawUnicodeString( pTarget->GetCaptionText() );
		vgui::surface()->DrawSetTextFont( m_hCaptionFont );
	}

	// Only draw drop shadow on PC because it looks crappy on a TV
	if ( !IsConsole() && locator_text_drop_shadow.GetBool() )
	{
		// Draw black text (drop shadow)
		vgui::surface()->DrawSetTextColor( 0,0,0, pTarget->m_alpha );
		vgui::surface()->DrawSetTextPos( x, y - (fontTall >>1) );
		vgui::surface()->DrawUnicodeString( pTarget->GetCaptionText() );
	}

	// Draw text
	vgui::surface()->DrawSetTextColor( pTarget->m_captionColor.r(),pTarget->m_captionColor.g(),pTarget->m_captionColor.b(), pTarget->m_alpha );

	if ( !bDrawMultiline )
	{
		vgui::surface()->DrawSetTextPos( x - 1, y - (fontTall >>1) - 1 );
		vgui::surface()->DrawUnicodeString( pTarget->GetCaptionText() );
	}
	else
	{
		int charX = x-1;
		int charY = y - ( fontTall >> 1 ) - 1;

		int iWidth = 0;

		const wchar_t *pString = pTarget->GetCaptionText();
		int len = Q_wcslen( pString );

		for ( int iChar = 0; iChar < len; ++ iChar )
		{
			int charW = vgui::surface()->GetCharacterWidth( m_hCaptionFont, pString[ iChar ] );
			iWidth += charW;

			if ( iWidth > pTarget->m_captionWide && pString[iChar] == L' ' )
			{
				charY += fontTall;
				charX = x-1;
				iWidth = 0;
			}

			vgui::surface()->DrawSetTextPos( charX, charY );
			vgui::surface()->DrawUnicodeChar( pString[iChar] );
			charX += charW;
		}		
	}
}

//-----------------------------------------------------------------------------
// Purpose: Figure out how wide (pixels) a string will be if rendered with this font
// 
//-----------------------------------------------------------------------------
int CLocatorPanel::GetScreenWidthForCaption( const wchar_t *pString, vgui::HFont hFont )
{
	int iWidth = 0;

	for ( int iChar = 0; iChar < Q_wcslen( pString ); ++ iChar )
	{
		iWidth += vgui::surface()->GetCharacterWidth( hFont, pString[ iChar ] );
	}

	return iWidth;
}

//-----------------------------------------------------------------------------
// Purpose: Some targets' captions contain information about key bindings that
//			should be displayed to the player. Do so.
//-----------------------------------------------------------------------------
void CLocatorPanel::DrawBindingName( CLocatorTarget *pTarget, const char *pchBindingName, int x, int y, bool bController )
{
	if ( !bController && !IsConsole() )
	{
		// Draw the caption
		vgui::surface()->DrawSetTextFont( m_hKeysFont );
		int fontTall = vgui::surface()->GetFontTall( m_hKeysFont );

		char szBinding[ 256 ];
		Q_strcpy( szBinding, pchBindingName ? pchBindingName : "" );

		if ( Q_strcmp( szBinding, "SEMICOLON" ) == 0 )
		{
			Q_strcpy( szBinding, ";" );
		}
		else if ( Q_strlen( szBinding ) == 1 && szBinding[ 0 ] >= 'a' && szBinding[ 0 ] <= 'z' )
		{
			// Make single letters uppercase
			szBinding[ 0 ] += ( 'A' - 'a' );
		}

		wchar_t wszCaption[ 64 ];
		g_pVGuiLocalize->ConstructString( wszCaption, sizeof(wchar_t)*64, szBinding, NULL );

		int iWidth = GetScreenWidthForCaption( wszCaption, m_hKeysFont );

		// Draw black text
		vgui::surface()->DrawSetTextColor( 0,0,0, pTarget->m_alpha );
		vgui::surface()->DrawSetTextPos( x - (iWidth>>1) - 1, y - (fontTall >>1) - 1 );
		vgui::surface()->DrawUnicodeString( wszCaption );
	}
	else
	{
		// Draw the caption
		wchar_t	wszCaption[ 64 ];

		vgui::surface()->DrawSetTextFont( BUTTON_FONT_HANDLE );
		int fontTall = vgui::surface()->GetFontTall( BUTTON_FONT_HANDLE );

		char szBinding[ 256 ];

		// Turn localized string into icon character
		Q_snprintf( szBinding, sizeof( szBinding ), "#GameUI_Icons_%s", pchBindingName );
		g_pVGuiLocalize->ConstructString( wszCaption, sizeof( wszCaption ), g_pVGuiLocalize->Find( szBinding ), 0 );
		g_pVGuiLocalize->ConvertUnicodeToANSI( wszCaption, szBinding, sizeof( szBinding ) );

		int iWidth = GetScreenWidthForCaption( wszCaption, BUTTON_FONT_HANDLE );

		int iLargeIconShift = MAX( 0, iWidth - ( ScreenWidth() * ICON_SIZE + ICON_GAP + ICON_GAP ) );

		// Draw the button
		vgui::surface()->DrawSetTextColor( 255,255,255, pTarget->m_alpha );
		vgui::surface()->DrawSetTextPos( x - (iWidth>>1) - iLargeIconShift, y - (fontTall >>1) );
		vgui::surface()->DrawUnicodeString( wszCaption );

	}
}

//-----------------------------------------------------------------------------
// Purpose: Draw an arrow to indicate that a target is offscreen
//
// iconWide is sent to this function so that the arrow knows how to straddle
// the icon that it is being drawn near.
//-----------------------------------------------------------------------------
void CLocatorPanel::DrawIndicatorArrow( int x, int y, int iconWide, int iconTall, int textWidth, int direction )
{
	int wide = iconWide;
	int tall = iconTall;

	//tall = wide = ScreenWidth() * ICON_SIZE;

	switch( direction )
	{
	case DRAW_ARROW_LEFT:
		vgui::surface()->DrawSetTexture( m_textureID_ArrowLeft );
		x -= wide;
		y += iconTall / 2 - tall / 2;
		vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall );
		break;

	case DRAW_ARROW_RIGHT:
		vgui::surface()->DrawSetTexture( m_textureID_ArrowRight );
		x += iconWide + textWidth;
		y += iconTall / 2 - tall / 2;
		vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall );
		break;

	case DRAW_ARROW_UP:
		vgui::surface()->DrawSetTexture( m_textureID_ArrowUp );
		x += iconWide / 2 - wide / 2;
		y -= tall;
		vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall );
		break;

	case DRAW_ARROW_DOWN:
		vgui::surface()->DrawSetTexture( m_textureID_ArrowDown );
		x += iconWide / 2 - wide / 2;
		y += iconTall;
		vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall );
		break;

	default:
		// Do not draw.
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Draws a very simple arrow that points down.
//-----------------------------------------------------------------------------
void CLocatorPanel::DrawSimpleArrow( int x, int y, int iconWide, int iconTall )
{
	vgui::surface()->DrawSetTexture( m_textureID_SimpleArrow );

	y += iconTall;

	vgui::surface()->DrawTexturedRect( x, y, x + iconWide, y + iconTall );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CLocatorPanel::GetIconPositionForOffscreenTarget( const Vector &vecDelta, float flDist, int *pXPos, int *pYPos )
{
	float xpos, ypos;
	float flRotation;
	float flRadius = YRES(OFFSCREEN_ICON_POSITION_RADIUS);

	if ( locator_topdown_style.GetBool() )
	{
		flRadius *= clamp( flDist / 600.0f, 1.75f, 3.0f );
	}

	GetTargetPosition( vecDelta, flRadius, &xpos, &ypos, &flRotation );

	*pXPos = xpos;
	*pYPos = ypos;
}

//-----------------------------------------------------------------------------
// Purpose: Given a handle, return the pointer to the proper locator target.
//-----------------------------------------------------------------------------
CLocatorTarget *CLocatorPanel::GetPointerForHandle( int hTarget )
{
	for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ )
	{
		if( m_targets[ i ].m_isActive && m_targets[ i ].m_serialNumber == hTarget )
		{
			return &m_targets[ i ];
		}
	}

	return NULL;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CLocatorPanel::AddTarget()
{
	for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ )
	{
		if( !m_targets[ i ].m_isActive )
		{
			m_targets[ i ].Activate( m_serializer );
			m_serializer++;

			return m_targets[ i ].m_serialNumber;
		}
	}

	DevWarning( "Locator Panel has no free targets!\n" );
	return -1;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CLocatorPanel::RemoveTarget( int hTarget )
{
	CLocatorTarget *pTarget = GetPointerForHandle( hTarget );

	if( pTarget )
	{
		pTarget->Deactivate();
	}
}

UtlSymbol

Now it is necessary to edit the file utlsymbol.h that is in publi /tier1/utlsymbol.h, once we have opened the file we eliminate TODO its content and paste the new code:

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Defines a symbol table
//
// $Header: $
// $NoKeywords: $
//===========================================================================//

#ifndef UTLSYMBOL_H
#define UTLSYMBOL_H

#ifdef _WIN32
#pragma once
#endif

#include "tier0/threadtools.h"
#include "tier1/utlrbtree.h"
#include "tier1/utlvector.h"
#include "tier1/utlbuffer.h"
#include "tier1/utllinkedlist.h"
#include "tier1/stringpool.h"


//-----------------------------------------------------------------------------
// forward declarations
//-----------------------------------------------------------------------------
class CUtlSymbolTable;
class CUtlSymbolTableMT;


//-----------------------------------------------------------------------------
// This is a symbol, which is a easier way of dealing with strings.
//-----------------------------------------------------------------------------
typedef unsigned short UtlSymId_t;

#define UTL_INVAL_SYMBOL  ((UtlSymId_t)~0)

class CUtlSymbol
{
public:
	// constructor, destructor
	CUtlSymbol() : m_Id(UTL_INVAL_SYMBOL) {}
	CUtlSymbol( UtlSymId_t id ) : m_Id(id) {}
	CUtlSymbol( const char* pStr );
	CUtlSymbol( CUtlSymbol const& sym ) : m_Id(sym.m_Id) {}
	
	// operator=
	CUtlSymbol& operator=( CUtlSymbol const& src ) { m_Id = src.m_Id; return *this; }
	
	// operator==
	bool operator==( CUtlSymbol const& src ) const { return m_Id == src.m_Id; }
	bool operator==( const char* pStr ) const;
	
	// Is valid?
	bool IsValid() const { return m_Id != UTL_INVAL_SYMBOL; }
	
	// Gets at the symbol
	operator UtlSymId_t const() const { return m_Id; }
	
	// Gets the string associated with the symbol
	const char* String( ) const;

	// Modules can choose to disable the static symbol table so to prevent accidental use of them.
	static void DisableStaticSymbolTable();
		
protected:
	UtlSymId_t   m_Id;
		
	// Initializes the symbol table
	static void Initialize();
	
	// returns the current symbol table
	static CUtlSymbolTableMT* CurrTable();
		
	// The standard global symbol table
	static CUtlSymbolTableMT* s_pSymbolTable; 

	static bool s_bAllowStaticSymbolTable;

	friend class CCleanupUtlSymbolTable;
};


//-----------------------------------------------------------------------------
// CUtlSymbolTable:
// description:
//    This class defines a symbol table, which allows us to perform mappings
//    of strings to symbols and back. The symbol class itself contains
//    a static version of this class for creating global strings, but this
//    class can also be instanced to create local symbol tables.
// 
//    This class stores the strings in a series of string pools. The first
//    two bytes of each string are decorated with a hash to speed up
//	  comparisons.
//-----------------------------------------------------------------------------

class CUtlSymbolTable
{
public:
	// constructor, destructor
	CUtlSymbolTable( int growSize = 0, int initSize = 16, bool caseInsensitive = false );
	~CUtlSymbolTable();
	
	// Finds and/or creates a symbol based on the string
	CUtlSymbol AddString( const char* pString );

	// Finds the symbol for pString
	CUtlSymbol Find( const char* pString ) const;
	
	// Look up the string associated with a particular symbol
	const char* String( CUtlSymbol id ) const;
	
	// Remove all symbols in the table.
	void  RemoveAll();

	int GetNumStrings( void ) const
	{
		return m_Lookup.Count();
	}

	// We store one of these at the beginning of every string to speed
	// up comparisons.
	typedef unsigned short hashDecoration_t; 

protected:
	class CStringPoolIndex
	{
	public:
		inline CStringPoolIndex()
		{
		}

		inline CStringPoolIndex( unsigned short iPool, unsigned short iOffset )
			: 	m_iPool(iPool), m_iOffset(iOffset)
		{}

		inline bool operator==( const CStringPoolIndex &other )	const
		{
			return m_iPool == other.m_iPool && m_iOffset == other.m_iOffset;
		}

		unsigned short m_iPool;		// Index into m_StringPools.
		unsigned short m_iOffset;	// Index into the string pool.
	};

	class CLess
	{
	public:
		CLess( int ignored = 0 ) {} // permits default initialization to NULL in CUtlRBTree
		bool operator!() const { return false; }
		bool operator()( const CStringPoolIndex &left, const CStringPoolIndex &right ) const;
	};

	// Stores the symbol lookup
	class CTree : public CUtlRBTree<CStringPoolIndex, unsigned short, CLess>
	{
	public:
		CTree(  int growSize, int initSize ) : CUtlRBTree<CStringPoolIndex, unsigned short, CLess>( growSize, initSize ) {}
		friend class CUtlSymbolTable::CLess; // Needed to allow CLess to calculate pointer to symbol table
	};

	struct StringPool_t
	{	
		int m_TotalLen;		// How large is 
		int m_SpaceUsed;
		char m_Data[1];
	};

	CTree m_Lookup;

	bool m_bInsensitive;
	mutable unsigned short m_nUserSearchStringHash;
	mutable const char* m_pUserSearchString;

	// stores the string data
	CUtlVector<StringPool_t*> m_StringPools;

private:
	int FindPoolWithSpace( int len ) const;
	const char* StringFromIndex( const CStringPoolIndex &index ) const;
	const char* DecoratedStringFromIndex( const CStringPoolIndex &index ) const;

	friend class CLess;
	friend class CSymbolHash;

};

class CUtlSymbolTableMT :  public CUtlSymbolTable
{
public:
	CUtlSymbolTableMT( int growSize = 0, int initSize = 32, bool caseInsensitive = false )
		: CUtlSymbolTable( growSize, initSize, caseInsensitive )
	{
	}

	CUtlSymbol AddString( const char* pString )
	{
		m_lock.LockForWrite();
		CUtlSymbol result = CUtlSymbolTable::AddString( pString );
		m_lock.UnlockWrite();
		return result;
	}

	CUtlSymbol Find( const char* pString ) const
	{
		m_lock.LockForWrite();
		CUtlSymbol result = CUtlSymbolTable::Find( pString );
		m_lock.UnlockWrite();
		return result;
	}

	const char* String( CUtlSymbol id ) const
	{
		m_lock.LockForRead();
		const char *pszResult = CUtlSymbolTable::String( id );
		m_lock.UnlockRead();
		return pszResult;
	}
	
private:
	mutable CThreadSpinRWLock m_lock;
};



//-----------------------------------------------------------------------------
// CUtlFilenameSymbolTable:
// description:
//    This class defines a symbol table of individual filenames, stored more
//	  efficiently than a standard symbol table.  Internally filenames are broken
//	  up into file and path entries, and a file handle class allows convenient 
//	  access to these.
//-----------------------------------------------------------------------------

// The handle is a CUtlSymbol for the dirname and the same for the filename, the accessor
//  copies them into a static char buffer for return.
typedef void* FileNameHandle_t;

// Symbol table for more efficiently storing filenames by breaking paths and filenames apart.
// Refactored from BaseFileSystem.h
class CUtlFilenameSymbolTable
{
	// Internal representation of a FileHandle_t
	// If we get more than 64K filenames, we'll have to revisit...
	// Right now CUtlSymbol is a short, so this packs into an int/void * pointer size...
	struct FileNameHandleInternal_t
	{
		FileNameHandleInternal_t()
		{
			path = 0;
			file = 0;
		}

		// Part before the final '/' character
		unsigned short path;
		// Part after the final '/', including extension
		unsigned short file;
	};

public:
	FileNameHandle_t	FindOrAddFileName( const char *pFileName );
	FileNameHandle_t	FindFileName( const char *pFileName );
	int					PathIndex(const FileNameHandle_t &handle) { return (( const FileNameHandleInternal_t * )&handle)->path; }
	bool				String( const FileNameHandle_t& handle, char *buf, int buflen );
	void				RemoveAll();
	void				SpewStrings();
	bool				SaveToBuffer( CUtlBuffer &buffer );
	bool				RestoreFromBuffer( CUtlBuffer &buffer );

private:
	CCountedStringPool	m_StringPool;
	mutable CThreadSpinRWLock m_lock;
};

// This creates a simple class that includes the underlying CUtlSymbol
//  as a private member and then instances a private symbol table to
//  manage those symbols.  Avoids the possibility of the code polluting the
//  'global'/default symbol table, while letting the code look like 
//  it's just using = and .String() to look at CUtlSymbol type objects
//
// NOTE:  You can't pass these objects between .dlls in an interface (also true of CUtlSymbol of course)
//
#define DECLARE_PRIVATE_SYMBOLTYPE( typename )			\
	class typename										\
	{													\
	public:												\
		typename();										\
		typename( const char* pStr );					\
		typename& operator=( typename const& src );		\
		bool operator==( typename const& src ) const;	\
		const char* String( ) const;					\
	private:											\
		CUtlSymbol m_SymbolId;							\
	};	

// Put this in the .cpp file that uses the above typename
#define IMPLEMENT_PRIVATE_SYMBOLTYPE( typename )					\
	static CUtlSymbolTable g_##typename##SymbolTable;				\
	typename::typename()											\
	{																\
		m_SymbolId = UTL_INVAL_SYMBOL;								\
	}																\
	typename::typename( const char* pStr )							\
	{																\
		m_SymbolId = g_##typename##SymbolTable.AddString( pStr );	\
	}																\
	typename& typename::operator=( typename const& src )			\
	{																\
		m_SymbolId = src.m_SymbolId;								\
		return *this;												\
	}																\
	bool typename::operator==( typename const& src ) const			\
	{																\
		return ( m_SymbolId == src.m_SymbolId );					\
	}																\
	const char* typename::String( ) const							\
	{																\
		return g_##typename##SymbolTable.String( m_SymbolId );		\
	}

#endif // UTLSYMBOL_H

To be honest, I do not understand much about this file and exactly what updates are made to it, however they are necessary for hud_locator_target.

C_GameInstructor and CBaseLesson

Now we will move to the real system of the Game Instructor, this is responsible for reading 2 files (scripts/instructor_lessons.txt and scripts /mod_lessons.txt) that are responsible for having the "lessons to be learned" that basically they are the events that the Game Instructor can show on the screen next to the options that go from the icon that will appear next to the message until when it will be marked as "lesson learned" or "lesson seen".

It can be said that it is like a kind of language that allows us to create personalized lessons that can be "learned" or simply closed when a certain condition is fulfilled within the code, something complicated to understand at the beginning but we will see an introduction of how to create them later on, Now let's go to the code of this system:

CBaseLesson

In summary it is the class responsible for containing all the information about each lesson that can appear with the Game Instructor. We will have to add these 2 files equally in the Client:

c_baselesson.h

//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose:		Client handler for instruction players how to play
//
//=============================================================================//

#ifndef _C_BASELESSON_H_
#define _C_BASELESSON_H_


#include "GameEventListener.h"
#include "hud_locator_target.h"


#define DECLARE_LESSON( _lessonClassName, _baseLessonClassName ) \
	typedef _baseLessonClassName BaseClass;\
	typedef _lessonClassName ThisClass;\
	_lessonClassName( const char *pchName, bool bIsDefaultHolder, bool bIsOpenOpportunity )\
		: _baseLessonClassName( pchName, bIsDefaultHolder, bIsOpenOpportunity )\
	{\
		Init();\
	}


enum LessonInstanceType
{
	LESSON_INSTANCE_MULTIPLE,
	LESSON_INSTANCE_SINGLE_OPEN,
	LESSON_INSTANCE_FIXED_REPLACE,
	LESSON_INSTANCE_SINGLE_ACTIVE,

	LESSON_INSTANCE_TYPE_TOTAL
};


// This is used to solve a problem where bots can take the place of a player, where on or the other don't have valid entities on the client at the same time
#define MAX_DELAYED_PLAYER_SWAPS 8

struct delayed_player_swap_t
{
	CHandle<C_BaseEntity> *phHandleToChange;
	int iNewUserID;

	delayed_player_swap_t( void )
	{
		phHandleToChange = NULL;
		iNewUserID = -1;
	}
};


abstract_class CBaseLesson : public CGameEventListener
{
public:
	CBaseLesson( const char *pchName, bool bIsDefaultHolder, bool bIsOpenOpportunity );
	virtual ~CBaseLesson( void );

	void AddPrerequisite( const char *pchLessonName );

	const CGameInstructorSymbol& GetNameSymbol( void ) const { return m_stringName; }
	const char * GetName( void ) const { return m_stringName.String(); }
	int GetPriority( void ) const { return m_iPriority;	}
	const char * GetCloseReason( void ) const { return m_stringCloseReason.String(); }
	void SetCloseReason( const char *pchReason ) { m_stringCloseReason = pchReason; }

	CBaseLesson* GetRoot( void ) const { return m_pRoot; }
	void SetRoot( CBaseLesson *pRoot );
	const CUtlVector < CBaseLesson * >* GetChildren( void ) const { return &m_OpenOpportunities; }

	float GetInitTime( void ) { return m_fInitTime; }
	void SetStartTime( void ) { m_fStartTime = gpGlobals->curtime; }
	void ResetStartTime( void ) { m_fStartTime = 0.0f; m_bHasPlayedSound = false; }

	bool ShouldShowSpew( void );
	bool NoPriority( void ) const;
	bool IsDefaultHolder( void ) const { return m_bIsDefaultHolder; }
	bool IsOpenOpportunity( void ) const { return m_bIsOpenOpportunity; }
	bool IsLocked( void ) const;
	bool CanOpenWhenDead( void ) const { return m_bCanOpenWhenDead; }
	bool IsInstructing( void ) const { return ( m_fStartTime > 0.0f ); }
	bool IsLearned( void ) const;
	bool PrerequisitesHaveBeenMet( void ) const;
	bool IsTimedOut( void );

	int InstanceType( void ) const { return m_iInstanceType; }
	const CGameInstructorSymbol& GetReplaceKeySymbol( void ) const { return m_stringReplaceKey; }
	const char* GetReplaceKey( void ) const { return m_stringReplaceKey.String(); }
	int GetFixedInstancesMax( void ) const { return m_iFixedInstancesMax; }
	bool ShouldReplaceOnlyWhenStopped( void ) const { return m_bReplaceOnlyWhenStopped; }
	void SetInstanceActive( bool bInstanceActive ) { m_bInstanceActive = bInstanceActive; }
	bool IsInstanceActive( void ) const { return m_bInstanceActive; }

	void ResetDisplaysAndSuccesses( void );
	bool IncDisplayCount( void );
	bool IncSuccessCount( void );
	void SetDisplayCount( int iDisplayCount ) { m_iDisplayCount = iDisplayCount; }
	void SetSuccessCount( int iSuccessCount ) { m_iSuccessCount = iSuccessCount; }
	int GetDisplayCount( void ) const { return m_iDisplayCount; }
	int GetSuccessCount( void ) const { return m_iSuccessCount; }
	int GetDisplayLimit( void ) const { return m_iDisplayLimit; }
	int GetSuccessLimit( void ) const { return m_iSuccessLimit; }

	void Init( void );	// NOT virtual, each constructor calls their own
	virtual void InitPrerequisites( void ) {};
	virtual void Start( void ) = 0;
	virtual void Stop( void ) = 0;
	virtual void OnOpen( void ) {};
	virtual void Update( void ) {};
	virtual void UpdateInactive( void ) {};

	virtual bool ShouldDisplay( void ) const { return true; }
	virtual bool IsVisible( void ) const { return true; }
	virtual bool WasDisplayed( void ) const { return m_bWasDisplayed ? true : false; }
	virtual void SwapOutPlayers( int iOldUserID, int iNewUserID ) {}
	virtual void TakePlaceOf( CBaseLesson *pLesson );

	const char *GetGroup() { return m_szLessonGroup.String(); }
	void	SetEnabled( bool bEnabled ) { m_bDisabled = !bEnabled; }

protected:
	void MarkSucceeded( void );
	void CloseOpportunity( const char *pchReason );
	bool DoDelayedPlayerSwaps( void ) const;

private:

	CBaseLesson							*m_pRoot;
	CUtlVector < CBaseLesson * >		m_OpenOpportunities;
	CUtlVector < const CBaseLesson * >	m_Prerequisites;

	CGameInstructorSymbol		m_stringCloseReason;
	CGameInstructorSymbol		m_stringName;

	bool		m_bInstanceActive : 1;
	bool		m_bSuccessCounted : 1;
	bool		m_bIsDefaultHolder : 1;
	bool		m_bIsOpenOpportunity : 1;

protected:
	LessonInstanceType	m_iInstanceType;

	int						m_iPriority;
	CGameInstructorSymbol	m_stringReplaceKey;
	int						m_iFixedInstancesMax;
	bool					m_bReplaceOnlyWhenStopped;
	int						m_iTeam;
	bool					m_bOnlyKeyboard;
	bool					m_bOnlyGamepad;

	int		m_iDisplayLimit;
	int		m_iDisplayCount;
	bool	m_bWasDisplayed;
	int		m_iSuccessLimit;
	int		m_iSuccessCount;

	float	m_fLockDuration;
	float	m_fTimeout;
	float	m_fInitTime;
	float	m_fStartTime;
	float	m_fLockTime;
	float	m_fUpdateInterval;
	bool 	m_bHasPlayedSound;

	CGameInstructorSymbol m_szStartSound;
	CGameInstructorSymbol m_szLessonGroup;

	bool	m_bCanOpenWhenDead;
	bool	m_bBumpWithTimeoutWhenLearned;
	bool	m_bCanTimeoutWhileInactive;
	bool	m_bDisabled;

	// Right now we can only queue up 4 swaps...
	// this number can be increased if more entity handle scripted variables are added
	mutable delayed_player_swap_t	m_pDelayedPlayerSwap[ MAX_DELAYED_PLAYER_SWAPS ];
	mutable int						m_iNumDelayedPlayerSwaps;

public:

	// Colors for console spew in verbose mode
	static Color	m_rgbaVerboseHeader;
	static Color	m_rgbaVerbosePlain;
	static Color	m_rgbaVerboseName;
	static Color	m_rgbaVerboseOpen;
	static Color	m_rgbaVerboseClose;
	static Color	m_rgbaVerboseSuccess;
	static Color	m_rgbaVerboseUpdate;
};


class CTextLesson : public CBaseLesson
{
public:
	DECLARE_LESSON( CTextLesson, CBaseLesson );

	void Init( void );	// NOT virtual, each constructor calls their own
	virtual void Start( void );
	virtual void Stop( void );

protected:

	CGameInstructorSymbol	m_szDisplayText;
	CGameInstructorSymbol	m_szDisplayParamText;
	CGameInstructorSymbol	m_szBinding;
	CGameInstructorSymbol	m_szGamepadBinding;
};


class CIconLesson : public CTextLesson
{
public:
	DECLARE_LESSON( CIconLesson, CTextLesson );

	void Init( void ); 	// NOT virtual, each constructor calls their own
	virtual void Start( void );
	virtual void Stop( void );
	virtual void Update( void );
	virtual void UpdateInactive( void );

	virtual bool ShouldDisplay( void ) const;
	virtual bool IsVisible( void ) const;
	virtual void SwapOutPlayers( int iOldUserID, int iNewUserID );
	virtual void TakePlaceOf( CBaseLesson *pLesson );

	void SetLocatorBinding( CLocatorTarget * pLocatorTarget );

	const char *GetCaptionColorString()	{ return m_szCaptionColor.String(); }

	bool IsPresentComplete( void );
	void PresentStart( void );
	void PresentEnd( void );

private:
	virtual void UpdateLocatorTarget( CLocatorTarget *pLocatorTarget, C_BaseEntity *pIconTarget );

protected:
	CHandle<C_BaseEntity>		m_hIconTarget;
	CGameInstructorSymbol		m_szVguiTargetName;
	CGameInstructorSymbol		m_szVguiTargetLookup;
	int		m_nVguiTargetEdge;
	float	m_flUpOffset;
	float	m_flRelativeUpOffset;
	float	m_fFixedPositionX;
	float	m_fFixedPositionY;

	int		m_hLocatorTarget;
	int		m_iFlags;

	float	m_fRange;
	float	m_fCurrentDistance;
	float	m_fOnScreenStartTime;
	float	m_fUpdateDistanceTime;

	CGameInstructorSymbol	m_szOnscreenIcon;
	CGameInstructorSymbol	m_szOffscreenIcon;
	CGameInstructorSymbol	m_szCaptionColor;

	bool	m_bFixedPosition;
	bool	m_bNoIconTarget;
	bool	m_bAllowNodrawTarget;
	bool	m_bVisible;
	bool	m_bShowWhenOccluded;
	bool	m_bNoOffscreen;
	bool	m_bForceCaption;
};

enum LessonAction
{
	LESSON_ACTION_NONE,

	LESSON_ACTION_SCOPE_IN,
	LESSON_ACTION_SCOPE_OUT,
	LESSON_ACTION_CLOSE,
	LESSON_ACTION_SUCCESS,
	LESSON_ACTION_LOCK,
	LESSON_ACTION_PRESENT_COMPLETE,
	LESSON_ACTION_PRESENT_START,
	LESSON_ACTION_PRESENT_END,

	LESSON_ACTION_REFERENCE_OPEN,

	LESSON_ACTION_SET,
	LESSON_ACTION_ADD,
	LESSON_ACTION_SUBTRACT,
	LESSON_ACTION_MULTIPLY,
	LESSON_ACTION_IS,
	LESSON_ACTION_LESS_THAN,
	LESSON_ACTION_HAS_PREFIX,
	LESSON_ACTION_HAS_BIT,
	LESSON_ACTION_BIT_COUNT_IS,
	LESSON_ACTION_BIT_COUNT_LESS_THAN,

	LESSON_ACTION_GET_DISTANCE,
	LESSON_ACTION_GET_ANGULAR_DISTANCE,
	LESSON_ACTION_GET_PLAYER_DISPLAY_NAME,
	LESSON_ACTION_CLASSNAME_IS,
	LESSON_ACTION_MODELNAME_IS,
	LESSON_ACTION_TEAM_IS,
	LESSON_ACTION_HEALTH_LESS_THAN,
	LESSON_ACTION_HEALTH_PERCENTAGE_LESS_THAN,
	LESSON_ACTION_GET_ACTIVE_WEAPON,
	LESSON_ACTION_WEAPON_IS,
	LESSON_ACTION_WEAPON_HAS,
	LESSON_ACTION_GET_ACTIVE_WEAPON_SLOT,
	LESSON_ACTION_GET_WEAPON_SLOT,
	LESSON_ACTION_GET_WEAPON_IN_SLOT,
	LESSON_ACTION_CLIP_PERCENTAGE_LESS_THAN,
	LESSON_ACTION_WEAPON_AMMO_LOW,
	LESSON_ACTION_WEAPON_AMMO_FULL,
	LESSON_ACTION_WEAPON_AMMO_EMPTY,
	LESSON_ACTION_WEAPON_CAN_USE,
	LESSON_ACTION_USE_TARGET_IS,
	LESSON_ACTION_GET_USE_TARGET,
	LESSON_ACTION_GET_POTENTIAL_USE_TARGET,

	// Enum continued in Mod_LessonAction
	LESSON_ACTION_MOD_START,
};

struct LessonElement_t
{
	int						iVariable;
	int						iParamVarIndex;
	int						iAction;
	_fieldtypes				paramType;
	CGameInstructorSymbol	szParam;
	bool					bNot : 1;
	bool					bOptionalParam : 1;

	LessonElement_t( int p_iVariable, int p_iAction, bool p_bNot, bool p_bOptionalParam, const char *pchParam, int p_iParamVarIndex, _fieldtypes p_paramType )
	{
		iVariable = p_iVariable;
		iAction = p_iAction;
		bNot = p_bNot;
		bOptionalParam = p_bOptionalParam;
		szParam = pchParam;
		iParamVarIndex = p_iParamVarIndex;
		paramType = p_paramType;
	}

	LessonElement_t( const LessonElement_t &p_LessonElement )
	{
		iVariable = p_LessonElement.iVariable;
		iAction = p_LessonElement.iAction;
		bNot = p_LessonElement.bNot;
		bOptionalParam = p_LessonElement.bOptionalParam;
		szParam = p_LessonElement.szParam;
		iParamVarIndex = p_LessonElement.iParamVarIndex;
		paramType = p_LessonElement.paramType;
	}
};

struct LessonEvent_t
{
	CUtlVector< LessonElement_t >	elements;
	CGameInstructorSymbol			szEventName;
};

class CScriptedIconLesson : public CIconLesson
{
public:
	DECLARE_LESSON( CScriptedIconLesson, CIconLesson )

	virtual ~CScriptedIconLesson( void );

	static void PreReadLessonsFromFile( void );
	static void Mod_PreReadLessonsFromFile( void );

	void Init( void );	// NOT virtual, each constructor calls their own
	virtual void InitPrerequisites( void );
	virtual void OnOpen( void );
	virtual void Update( void );

	virtual void SwapOutPlayers( int iOldUserID, int iNewUserID );

	virtual void FireGameEvent( IGameEvent *event );
	virtual void ProcessOpenGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event );
	virtual void ProcessCloseGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event );
	virtual void ProcessSuccessGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event );

	CUtlVector< LessonEvent_t >& GetOpenEvents( void ) { return m_OpenEvents; }
	CUtlVector< LessonEvent_t >& GetCloseEvents( void ) { return m_CloseEvents; }
	CUtlVector< LessonEvent_t >& GetSuccessEvents( void ) { return m_SuccessEvents; }
	CUtlVector< LessonEvent_t >& GetOnOpenEvents( void ) { return m_OnOpenEvents; }
	CUtlVector< LessonEvent_t >& GetUpdateEvents( void ) { return m_UpdateEvents; }

	bool ProcessElements( IGameEvent *event, const CUtlVector< LessonElement_t > *pElements );

private:
	void InitElementsFromKeys( CUtlVector< LessonElement_t > *pLessonElements, KeyValues *pKey );
	void InitElementsFromElements( CUtlVector< LessonElement_t > *pLessonElements, const CUtlVector< LessonElement_t > *pLessonElements2 );

	void InitFromKeys( KeyValues *pKey );

	bool ProcessElement( IGameEvent *event, const LessonElement_t *pLessonElement, bool bInFailedScope );

	bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, float &bVar, const CGameInstructorSymbol *pchParamName, float fParam );
	bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, int &bVar, const CGameInstructorSymbol *pchParamName, float fParam );
	bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, bool &bVar, const CGameInstructorSymbol *pchParamName, float fParam );
	bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, EHANDLE &hVar, const CGameInstructorSymbol *pchParamName, float fParam, C_BaseEntity *pParam, const char *pchParam );
	bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, CGameInstructorSymbol *pchVar, const CGameInstructorSymbol *pchParamName, const char *pchParam );

	// Implemented per mod so they can have custom actions
	bool Mod_ProcessElementAction( int iAction, bool bNot, const char *pchVarName, EHANDLE &hVar, const CGameInstructorSymbol *pchParamName, float fParam, C_BaseEntity *pParam, const char *pchParam, bool &bModHandled );

	LessonEvent_t * AddOpenEvent( void );
	LessonEvent_t * AddCloseEvent( void );
	LessonEvent_t * AddSuccessEvent( void );
	LessonEvent_t * AddOnOpenEvent( void );
	LessonEvent_t * AddUpdateEvent( void );

private:
	static CUtlDict< int, int > CScriptedIconLesson::LessonActionMap;

	EHANDLE					m_hLocalPlayer;
	float					m_fOutput;
	CHandle<C_BaseEntity>	m_hEntity1;
	CHandle<C_BaseEntity>	m_hEntity2;
	CGameInstructorSymbol	m_szString1;
	CGameInstructorSymbol	m_szString2;
	int						m_iInteger1;
	int						m_iInteger2;
	float					m_fFloat1;
	float					m_fFloat2;

	CUtlVector< CGameInstructorSymbol >	m_PrerequisiteNames;
	CUtlVector< LessonEvent_t >	m_OpenEvents;
	CUtlVector< LessonEvent_t >	m_CloseEvents;
	CUtlVector< LessonEvent_t >	m_SuccessEvents;
	CUtlVector< LessonEvent_t >	m_OnOpenEvents;
	CUtlVector< LessonEvent_t >	m_UpdateEvents;

	float					m_fUpdateEventTime;
	CScriptedIconLesson		*m_pDefaultHolder;

	int		m_iScopeDepth;

	// Need this to get offsets to scripted variables
	friend class LessonVariableInfo;
	friend int LessonActionFromString( const char *pchName );
};


#endif // _C_BASELESSON_H_

c_baselesson.cpp

//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose:		Client handler implementations for instruction players how to play
//
//=============================================================================//

#include "cbase.h"

#include "c_baselesson.h"
#include "c_gameinstructor.h"

#include "hud_locator_target.h"
#include "c_world.h"
#include "iinput.h"
#include "ammodef.h"
#include "vprof.h"
#include "view.h"
#include "vstdlib/ikeyvaluessystem.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//=========================================================
// Configuración
//=========================================================

#define LESSON_PRIORITY_MAX 1000
#define LESSON_PRIORITY_NONE 0
#define LESSON_MIN_TIME_ON_SCREEN_TO_MARK_DISPLAYED 1.5f
#define LESSON_MIN_TIME_BEFORE_LOCK_ALLOWED 0.1f
#define LESSON_DISTANCE_UPDATE_RATE 0.25f

// See comments in UtlSymbol on why this is useful and how it works
IMPLEMENT_PRIVATE_SYMBOLTYPE( CGameInstructorSymbol );

extern ConVar gameinstructor_verbose;
extern ConVar gameinstructor_verbose_lesson;
extern ConVar gameinstructor_find_errors;

//
// CGameInstructorLesson
//

Color CBaseLesson::m_rgbaVerboseHeader = Color( 255, 128, 64, 255 );
Color CBaseLesson::m_rgbaVerbosePlain = Color( 64, 128, 255, 255 );
Color CBaseLesson::m_rgbaVerboseName = Color( 255, 255, 255, 255 );
Color CBaseLesson::m_rgbaVerboseOpen = Color( 0, 255, 0, 255 );
Color CBaseLesson::m_rgbaVerboseClose = Color( 255, 0, 0, 255 );
Color CBaseLesson::m_rgbaVerboseSuccess = Color( 255, 255, 0, 255 );
Color CBaseLesson::m_rgbaVerboseUpdate = Color( 255, 0, 255, 255  );


//=========================================================
// Constructor
//=========================================================
CBaseLesson::CBaseLesson( const char *pchName, bool bIsDefaultHolder, bool bIsOpenOpportunity )
{
	COMPILE_TIME_ASSERT( sizeof( CGameInstructorSymbol ) == sizeof( CUtlSymbol ) );

	m_stringName			= pchName;
	m_stringReplaceKey		= "";
	m_bIsDefaultHolder		= bIsDefaultHolder;
	m_bIsOpenOpportunity	= bIsOpenOpportunity;

	Init();
}

//=========================================================
// Destructor
//=========================================================
CBaseLesson::~CBaseLesson()
{
	// Remove from root's children list
	if ( m_pRoot )
		m_pRoot->m_OpenOpportunities.FindAndRemove(this);

	else
	{
		for ( int i = 0; i < m_OpenOpportunities.Count(); ++i )
		{
			// Remove from children if they are still around
			CBaseLesson *pLesson	= m_OpenOpportunities[ i ];
			pLesson->m_pRoot			= NULL;
		}
	}
}

//=========================================================
//=========================================================
void CBaseLesson::AddPrerequisite( const char *pchLessonName )
{
	if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "\t%s: ", GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Adding prereq " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pchLessonName );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" );
	}

	const CBaseLesson *pPrerequisite = GetGameInstructor().GetLesson( pchLessonName );

	if ( !pPrerequisite )
	{
		DevWarning( "Prerequisite %s added by lesson %s doesn't exist!\n", pchLessonName, GetName() );
		return;
	}

	m_Prerequisites.AddToTail(pPrerequisite);
}

//=========================================================
//=========================================================
void CBaseLesson::SetRoot( CBaseLesson *pRoot )
{
	m_pRoot = pRoot;

	if ( m_pRoot->m_OpenOpportunities.Find( this ) == -1 )
		m_pRoot->m_OpenOpportunities.AddToTail( this );
}

//=========================================================
//=========================================================
bool CBaseLesson::ShouldShowSpew()
{
	// @DEBUG
	return true;

	if ( gameinstructor_verbose_lesson.GetString()[ 0 ] == '\0' )
		return false;

	return ( Q_stristr( GetName(), gameinstructor_verbose_lesson.GetString() ) != NULL );
}

//=========================================================
//=========================================================
bool CBaseLesson::NoPriority() const
{
	return ( m_iPriority == LESSON_PRIORITY_NONE );
}

//=========================================================
//=========================================================
bool CBaseLesson::IsLocked() const
{
	if ( m_fLockDuration == 0.0f )
		return false;

	if ( !IsInstructing() || !IsVisible() )
		return false;

	float fLockTime = m_fLockTime;

	if ( fLockTime == 0.0f )
		fLockTime = m_fStartTime;

	return ( gpGlobals->curtime > m_fStartTime + LESSON_MIN_TIME_BEFORE_LOCK_ALLOWED && gpGlobals->curtime < fLockTime + m_fLockDuration );
}

//=========================================================
//=========================================================
bool CBaseLesson::IsLearned() const
{
	if ( m_iDisplayLimit > 0 && m_iDisplayCount >= m_iDisplayLimit )
		return true;

	if ( m_iSuccessLimit > 0 && m_iSuccessCount >= m_iSuccessLimit )
		return true;

	return false;
}

//=========================================================
//=========================================================
bool CBaseLesson::PrerequisitesHaveBeenMet() const
{
	for ( int i = 0; i < m_Prerequisites.Count(); ++i )
	{
		if ( !m_Prerequisites[ i ]->IsLearned() )
		{
			// Failed a prereq
			return false;
		}
	}

	// All prereqs passed
	return true;
}

//=========================================================
//=========================================================
bool CBaseLesson::IsTimedOut()
{
	VPROF_BUDGET( "CBaseLesson::IsTimedOut", "GameInstructor" );

	// Check for no timeout
	if ( m_fTimeout == 0.0f )
		return false;

	float fStartTime = m_fStartTime;

	if ( GetRoot()->IsLearned() )
	{
		if ( !m_bBumpWithTimeoutWhenLearned )
		{
			// Time out instantly if we've learned this and don't want to keep it open for priority bumping
			return true;
		}
		else
		{
			// It'll never be active, so lets use timeout based on when it was initialized
			fStartTime = m_fInitTime;
		}
	}

	if ( !fStartTime )
	{
		if ( !m_bCanTimeoutWhileInactive )
		{
			return false;
		}

		// Not active, so lets use timeout based on when it was initialized
		fStartTime = m_fInitTime;
	}

	bool bTimedOut = ( fStartTime + m_fTimeout < gpGlobals->curtime );

	if ( bTimedOut )
		SetCloseReason( "Timed out." );

	return bTimedOut;
}

//=========================================================
//=========================================================
void CBaseLesson::ResetDisplaysAndSuccesses()
{
	m_iDisplayCount		= 0;
	m_bSuccessCounted	= false;
	m_iSuccessCount		= 0;
}

//=========================================================
//=========================================================
bool CBaseLesson::IncDisplayCount()
{
	if ( m_iDisplayCount < m_iDisplayLimit )
	{
		m_iDisplayCount++;
		return true;
	}

	return false;
}

//=========================================================
//=========================================================
bool CBaseLesson::IncSuccessCount()
{
	if ( m_iSuccessCount < m_iSuccessLimit )
	{
		m_iSuccessCount++;
		return true;
	}

	return false;
}

//=========================================================
//=========================================================
void CBaseLesson::Init()
{
	m_pRoot				= NULL;
	m_bSuccessCounted	= false;

	SetCloseReason( "None given." );

	m_iPriority					= LESSON_PRIORITY_MAX; // Set to invalid value to ensure that it is actually set later on
	m_iInstanceType				= LESSON_INSTANCE_MULTIPLE;
	m_iFixedInstancesMax		= 1;
	m_bReplaceOnlyWhenStopped	= false;
	m_iTeam						= TEAM_ANY;
	m_bOnlyKeyboard				= false;
	m_bOnlyGamepad				= false;

	m_iDisplayLimit				= 0;
	m_iDisplayCount				= 0;
	m_bWasDisplayed				= false;

	m_iSuccessLimit				= 0;
	m_iSuccessCount				= 0;

	m_fLockDuration				= 0.0f;
	m_bCanOpenWhenDead			= false;
	m_bBumpWithTimeoutWhenLearned = false;
	m_bCanTimeoutWhileInactive	= false;
	m_fTimeout					= 0.0f;

	m_fInitTime					= gpGlobals->curtime;
	m_fStartTime				= 0.0f;
	m_fLockTime					= 0.0f;

	m_fUpdateInterval			= 0.5;
	m_bHasPlayedSound			= false;

	m_szStartSound				= "Instructor.LessonStart";
	m_szLessonGroup				= "";

	m_iNumDelayedPlayerSwaps = 0;
}

//=========================================================
//=========================================================
void CBaseLesson::TakePlaceOf( CBaseLesson *pLesson )
{
	// Transfer over marked as displayed so a replaced lesson won't count as an extra display
	m_bWasDisplayed				= pLesson->m_bWasDisplayed;
	pLesson->m_bWasDisplayed		= false;
}

//=========================================================
//=========================================================
void CBaseLesson::MarkSucceeded()
{
	if ( !m_bSuccessCounted )
	{
		GetGameInstructor().MarkSucceeded( GetName() );
		m_bSuccessCounted = true;
	}
}

//=========================================================
//=========================================================
void CBaseLesson::CloseOpportunity( const char *pchReason )
{
	SetCloseReason(pchReason);
	m_bIsOpenOpportunity = false;
}

//=========================================================
//=========================================================
bool CBaseLesson::DoDelayedPlayerSwaps() const
{
	// A bot has swapped places with a player or player with a bot...
	// At the time of the actual swap there was no client representation for the new player...
	// So that swap was queued up and now we're going to make things right!
	while ( m_iNumDelayedPlayerSwaps )
	{
		C_BasePlayer *pNewPlayer = UTIL_PlayerByUserId( m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps - 1 ].iNewUserID );

		if ( !pNewPlayer )
		{
			// There is still no client representation of the new player, we'll have to try again later
			if ( gameinstructor_verbose.GetInt() > 1 )
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tFailed delayed player swap!" );
			
			return false;
		}

		if ( gameinstructor_verbose.GetInt() > 1 )
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tSuccessful delayed player swap!" );

		m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps - 1 ].phHandleToChange->Set( pNewPlayer );
		m_iNumDelayedPlayerSwaps--;
	}

	return true;
}


//
// CTextLesson
//

//=========================================================
//=========================================================
void CTextLesson::Init()
{
	m_szDisplayText			= "";
	m_szDisplayParamText	= "";
	m_szBinding				= "";
	m_szGamepadBinding		= "";
}

//=========================================================
//=========================================================
void CTextLesson::Start()
{
	// TODO: Display some text
	//m_szDisplayText
}

//=========================================================
//=========================================================
void CTextLesson::Stop()
{
	// TODO: Clean up text
}

//
// CIconLesson
//

void CIconLesson::Init()
{
	m_hIconTarget			= NULL;
	m_szVguiTargetName		= "";
	m_szVguiTargetLookup	= "";
	m_nVguiTargetEdge		= 0;

	m_hLocatorTarget		= -1;
	m_bFixedPosition		= false;
	m_bNoIconTarget			= false;
	m_bAllowNodrawTarget	= false;

	m_bVisible				= true;
	m_bShowWhenOccluded		= true;
	m_bNoOffscreen			= false;
	m_bForceCaption			= false;

	m_szOnscreenIcon		= "";
	m_szOffscreenIcon		= "";

	m_flUpOffset			= 0.0f;
	m_flRelativeUpOffset	= 0.0f;
	m_fFixedPositionX		= 0.0f;
	m_fFixedPositionY		= 0.0f;

	m_fRange				= 0.0f;
	m_fCurrentDistance		= 0.0f;

	m_fOnScreenStartTime	= 0.0f;
	m_fUpdateDistanceTime	= 0.0f;

	m_iFlags				= LOCATOR_ICON_FX_NONE;
	m_szCaptionColor		= "255,255,255";// Default to white
}

//=========================================================
//=========================================================
void CIconLesson::Start()
{
	if ( !DoDelayedPlayerSwaps() )
		return;

	// Display some text
	C_BaseEntity *pIconTarget = m_hIconTarget.Get();

	if ( !pIconTarget )	
	{
		if ( !m_bNoIconTarget )
		{
			// Wanted one, but couldn't get it
			CloseOpportunity( "Icon Target handle went invalid before the lesson started!" );
		}

		return;
	}
	else
	{
		if ( ( pIconTarget->IsEffectActive( EF_NODRAW ) || pIconTarget->IsDormant() ) && !m_bAllowNodrawTarget )
		{
			// We don't allow no draw entities
			CloseOpportunity( "Icon Target is using effect NODRAW and allow_nodraw_target is false!" );
			return;
		}
	}

	CLocatorTarget *pLocatorTarget = NULL;

	if ( m_hLocatorTarget != -1 )
	{
		// Lets try the handle that we've held on to
		pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );

		if ( !pLocatorTarget )
		{
			// It's gone stale, get a new target
			m_hLocatorTarget = Locator_AddTarget();
			pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );
		}
	}
	else
	{
		// Get a new target
		m_hLocatorTarget = Locator_AddTarget();
		pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );
	}

	if( m_hLocatorTarget == -1 || !pLocatorTarget )
	{
		CloseOpportunity( "Could not get a handle for new locator target. Too many targets in use!" );
		return;
	}

	pLocatorTarget->AddIconEffects( m_iFlags );
	pLocatorTarget->SetCaptionColor( GetCaptionColorString() );
	UpdateLocatorTarget( pLocatorTarget, pIconTarget );

	// Update occlusion data
	Locator_ComputeTargetIconPositionFromHandle( m_hLocatorTarget );
}

//=========================================================
//=========================================================
void CIconLesson::Stop()
{
	if ( !DoDelayedPlayerSwaps() )
		return;

	if ( m_hLocatorTarget != -1 )
		Locator_RemoveTarget( m_hLocatorTarget );

	m_fOnScreenStartTime = 0.0f;
}

//=========================================================
//=========================================================
void CIconLesson::Update()
{
	if ( !DoDelayedPlayerSwaps() )
		return;

	C_BaseEntity *pIconTarget = m_hIconTarget.Get();

	if ( !pIconTarget )
	{
		if ( !m_bNoIconTarget )
		{
			CloseOpportunity( "Lost our icon target handle returned NULL." );
		}

		return;
	}
	else
	{
		if ( ( pIconTarget->IsEffectActive( EF_NODRAW ) || pIconTarget->IsDormant() ) && !m_bAllowNodrawTarget )
		{
			// We don't allow no draw entities
			CloseOpportunity( "Icon Target is using effect NODRAW and allow_nodraw_target is false!" );
			return;
		}
	}

	CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );
	if ( !pLocatorTarget )
	{
		// Temp instrumentation to catch a bug - possibly calling Update without having called Start?
		Warning( "Problem in lesson %s: Locator_GetTargetFromHandle returned null for handle %d.\n IsInstanceActive: %s. IsInstructing: %s. IsLearned: %s\n",
				GetName(), m_hLocatorTarget, 
				(IsInstanceActive() ? "yes" : "no"),
				(IsInstructing() ? "yes" : "no"),
				(IsLearned() ? "yes" : "no") );
		CloseOpportunity( "Lost locator target handle." );
		return;
	}

	UpdateLocatorTarget( pLocatorTarget, pIconTarget );
	C_BasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer();

	// Check if it has been onscreen long enough to count as being displayed
	if ( m_fOnScreenStartTime == 0.0f )
	{
		if ( pLocatorTarget->IsOnScreen() && ( IsPresentComplete() || ( pLocatorTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_STATIC ) ) )
		{
			// Is either static or has finished presenting and is on screen
			m_fOnScreenStartTime = gpGlobals->curtime;
		}
	}
	else
	{
		if ( !pLocatorTarget->IsOnScreen() )
		{
			// Was visible before, but it isn't now
			m_fOnScreenStartTime = 0.0f;
		}
		else if ( gpGlobals->curtime - m_fOnScreenStartTime >= LESSON_MIN_TIME_ON_SCREEN_TO_MARK_DISPLAYED )
		{
			// Lesson on screen long enough to be counted as displayed
			m_bWasDisplayed = true;
		}
	}

	if ( m_fUpdateDistanceTime < gpGlobals->curtime )
	{
		// Update it's distance from the local player
		C_BaseEntity *pTarget = m_hIconTarget.Get();

		if ( !pLocalPlayer || !pTarget || pLocalPlayer == pTarget )
		{
			m_fCurrentDistance = 0.0f;
		}
		else
		{
			m_fCurrentDistance = pLocalPlayer->EyePosition().DistTo( pTarget->WorldSpaceCenter() );
		}

		m_fUpdateDistanceTime = gpGlobals->curtime + LESSON_DISTANCE_UPDATE_RATE;
	}
}

void CIconLesson::UpdateInactive()
{
	if ( m_fUpdateDistanceTime < gpGlobals->curtime )
	{
		if ( !DoDelayedPlayerSwaps() )
		{
			return;
		}

		C_BaseEntity *pIconTarget = m_hIconTarget.Get();

		if ( !pIconTarget )
		{
			if ( !m_bNoIconTarget )
			{
				CloseOpportunity( "Lost our icon target handle returned NULL." );
			}

			m_fCurrentDistance = 0.0f;
			return;
		}
		else
		{
			if ( ( pIconTarget->IsEffectActive( EF_NODRAW ) || pIconTarget->IsDormant() ) && !m_bAllowNodrawTarget )
			{
				// We don't allow no draw entities
				CloseOpportunity( "Icon Target is using effect NODRAW and allow_nodraw_target is false!" );
				return;
			}
		}

		// Update it's distance from the local player
		C_BasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer();

		if ( !pLocalPlayer || pLocalPlayer == pIconTarget )
		{
			m_fCurrentDistance = 0.0f;
		}
		else
		{
			m_fCurrentDistance = pLocalPlayer->EyePosition().DistTo( pIconTarget->WorldSpaceCenter() );
		}

		m_fUpdateDistanceTime = gpGlobals->curtime + LESSON_DISTANCE_UPDATE_RATE;
	}
}

bool CIconLesson::ShouldDisplay() const
{
	VPROF_BUDGET( "CIconLesson::ShouldDisplay", "GameInstructor" );

	if ( !DoDelayedPlayerSwaps() )
	{
		return false;
	}

	if ( m_fRange > 0.0f && m_fCurrentDistance > m_fRange )
	{
		// Distance to target is more than the max range
		return false;
	}

	if ( !m_bShowWhenOccluded && m_hLocatorTarget >= 0 )
	{
		CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );

		if ( pLocatorTarget && pLocatorTarget->IsOccluded() )
		{
			// Target is occluded and doesn't want to be shown when occluded
			return false;
		}
	}

	// Ok to display
	return true;
}

bool CIconLesson::IsVisible() const
{
	VPROF_BUDGET( "CIconLesson::IsVisible", "GameInstructor" );

	if( m_hLocatorTarget == -1 )
	{
		// If it doesn't want a target, it's "visible" otherwise we'll have to call it invisible
		return m_bNoIconTarget;
	}

	CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );
	if ( !pLocatorTarget )
	{
		return false;
	}

	return pLocatorTarget->IsVisible();
}

void CIconLesson::SwapOutPlayers( int iOldUserID, int iNewUserID )
{
	BaseClass::SwapOutPlayers( iOldUserID, iNewUserID );

	if ( m_bNoIconTarget )
		return;

	// Get the player pointers from the user IDs
	C_BasePlayer *pOldPlayer = UTIL_PlayerByUserId( iOldUserID );
	C_BasePlayer *pNewPlayer = UTIL_PlayerByUserId( iNewUserID );

	if ( pOldPlayer == m_hIconTarget.Get() )
	{
		if ( pNewPlayer )
		{
			m_hIconTarget = pNewPlayer;
		}
		else
		{
			if ( m_iNumDelayedPlayerSwaps < MAX_DELAYED_PLAYER_SWAPS )
			{
				m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].phHandleToChange = &m_hIconTarget;
				m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].iNewUserID = iNewUserID;
				++m_iNumDelayedPlayerSwaps;
			}
		}
	}
}

void CIconLesson::TakePlaceOf( CBaseLesson *pLesson )
{
	BaseClass::TakePlaceOf( pLesson );

	const CIconLesson *pIconLesson = dynamic_cast<const CIconLesson*>( pLesson );

	if ( pIconLesson )
	{
		if ( pIconLesson->m_hLocatorTarget != -1 )
		{
			CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( pIconLesson->m_hLocatorTarget );

			if ( pLocatorTarget )
			{
				// This one draw right to the hud... use it's icon target handle
				m_hLocatorTarget = pIconLesson->m_hLocatorTarget;
			}
		}

		m_fOnScreenStartTime = pIconLesson->m_fOnScreenStartTime;
	}
}

void CIconLesson::SetLocatorBinding( CLocatorTarget * pLocatorTarget )
{
	if ( IsX360() /*|| input->ControllerModeActive()*/ )
	{
		// Try to use gamepad bindings first
		if ( m_szGamepadBinding.String()[ 0 ] != '\0' )
		{
			// Found gamepad binds!
			pLocatorTarget->SetBinding( m_szGamepadBinding.String() );
		}
		else
		{
			// No gamepad binding, so fallback to the regular binding
			pLocatorTarget->SetBinding( m_szBinding.String() );
		}
	}
	else
	{
		// Always use the regular binding when the gamepad is disabled
		pLocatorTarget->SetBinding( m_szBinding.String() );
	}
}

bool CIconLesson::IsPresentComplete()
{
	if ( m_hLocatorTarget == -1 )
		return false;

	CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );

	if ( !pLocatorTarget )
		return false;

	return !pLocatorTarget->IsPresenting();
}

void CIconLesson::PresentStart()
{
	if ( m_hLocatorTarget == -1 )
		return;

	CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );

	if ( !pLocatorTarget )
		return;

	pLocatorTarget->StartPresent();
}

void CIconLesson::PresentEnd()
{
	if ( m_hLocatorTarget == -1 )
		return;

	CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget );

	if ( !pLocatorTarget )
		return;

	pLocatorTarget->EndPresent();
}

void CIconLesson::UpdateLocatorTarget( CLocatorTarget *pLocatorTarget, C_BaseEntity *pIconTarget )
{
	if ( m_bFixedPosition )
	{
		pLocatorTarget->m_bOriginInScreenspace = true;
		pLocatorTarget->m_vecOrigin.x = m_fFixedPositionX;
		pLocatorTarget->m_vecOrigin.y = m_fFixedPositionY;
		pLocatorTarget->SetVguiTargetName( m_szVguiTargetName.String() );
		pLocatorTarget->SetVguiTargetLookup( m_szVguiTargetLookup.String() );
		pLocatorTarget->SetVguiTargetEdge( m_nVguiTargetEdge );
	}
	else
	{
		pLocatorTarget->m_bOriginInScreenspace = false;
		pLocatorTarget->m_vecOrigin = pIconTarget->EyePosition() + MainViewUp() * m_flRelativeUpOffset + Vector( 0.0f, 0.0f, m_flUpOffset );
		pLocatorTarget->SetVguiTargetName( "" );
	}

	const char *pchDisplayParamText = m_szDisplayParamText.String();
#ifdef INFESTED_DLL
	char szCustomName[ 256 ];
#endif

	// Check if the parameter is the be the player display name
	if ( Q_stricmp( pchDisplayParamText, "use_name" ) == 0 )
	{
		// Fix up the player display name
		C_BasePlayer *pPlayer = ToBasePlayer( pIconTarget );
		if ( pPlayer )
		{
			pchDisplayParamText = pPlayer->GetPlayerName();
		}
		else
		{
			bool bNoName = true;

#ifdef INFESTED_DLL
			C_ASW_Marine *pMarine = dynamic_cast< C_ASW_Marine* >( pIconTarget );
			if ( pMarine )
			{
				C_ASW_Marine_Resource *pMR = pMarine->GetMarineResource();
				if ( pMR )
				{
					pMR->GetDisplayName( szCustomName, sizeof( szCustomName ) );
					pchDisplayParamText = szCustomName;
					bNoName = false;
				}
			}
#endif

			if ( bNoName )
			{
				// It's not a player!
				pchDisplayParamText = "";
			}
		}
	}

	pLocatorTarget->SetCaptionText( m_szDisplayText.String(), pchDisplayParamText );
	SetLocatorBinding( pLocatorTarget );
	pLocatorTarget->SetOnscreenIconTextureName( m_szOnscreenIcon.String() );
	pLocatorTarget->SetOffscreenIconTextureName( m_szOffscreenIcon.String() );
	pLocatorTarget->SetVisible( m_bVisible );

	C_BasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer();

	if( !m_bFixedPosition && 
		( ( pLocalPlayer != NULL && pLocalPlayer == m_hIconTarget ) || 
		  GetClientWorldEntity() == m_hIconTarget ) )
	{
		// Mark this icon as a static icon that draws in a fixed 
		// location on the hud rather than tracking an object
		// in 3D space.
		pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_STATIC );
	}
	else
	{
		pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_NONE );
	}

	if ( m_bNoOffscreen )
	{
		pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_NO_OFFSCREEN );
	}
	else
	{
		pLocatorTarget->RemoveIconEffects( LOCATOR_ICON_FX_NO_OFFSCREEN );
	}

	if( m_bForceCaption || IsLocked() )
	{
		pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_FORCE_CAPTION );
	}
	else
	{
		pLocatorTarget->RemoveIconEffects( LOCATOR_ICON_FX_FORCE_CAPTION );
	}

	pLocatorTarget->Update();

	if ( pLocatorTarget->m_bIsDrawing )
	{
		if ( !m_bHasPlayedSound )
		{
			GetGameInstructor().PlaySound( m_szStartSound.String() );
			m_bHasPlayedSound = true;
		}
	}
}

//
// CScriptedIconLesson
//

// Linking variables to scriptable entries is done here!
// The first parameter correlates to the case insensitive string name read from scripts.
// This macro generates code that passes this consistent variable data in to other macros
#define LESSON_VARIABLE_FACTORY \
	LESSON_VARIABLE_MACRO_EHANDLE( VOID, m_hLocalPlayer, EHANDLE )	\
																	\
	LESSON_VARIABLE_MACRO_EHANDLE( LOCAL_PLAYER, m_hLocalPlayer, EHANDLE )	\
	LESSON_VARIABLE_MACRO( OUTPUT, m_fOutput, float )						\
																			\
	LESSON_VARIABLE_MACRO_EHANDLE( ENTITY1, m_hEntity1, EHANDLE )				\
	LESSON_VARIABLE_MACRO_EHANDLE( ENTITY2, m_hEntity2, EHANDLE )				\
	LESSON_VARIABLE_MACRO_STRING( STRING1, m_szString1, CGameInstructorSymbol )	\
	LESSON_VARIABLE_MACRO_STRING( STRING2, m_szString2, CGameInstructorSymbol )	\
	LESSON_VARIABLE_MACRO( INTEGER1, m_iInteger1, int )							\
	LESSON_VARIABLE_MACRO( INTEGER2, m_iInteger2, int )							\
	LESSON_VARIABLE_MACRO( FLOAT1, m_fFloat1, float )							\
	LESSON_VARIABLE_MACRO( FLOAT2, m_fFloat2, float )							\
																				\
	LESSON_VARIABLE_MACRO_EHANDLE( ICON_TARGET, m_hIconTarget, EHANDLE )							\
	LESSON_VARIABLE_MACRO_STRING( VGUI_TARGET_NAME, m_szVguiTargetName, CGameInstructorSymbol )		\
	LESSON_VARIABLE_MACRO_STRING( VGUI_TARGET_LOOKUP, m_szVguiTargetLookup, CGameInstructorSymbol )	\
	LESSON_VARIABLE_MACRO( VGUI_TARGET_EDGE, m_nVguiTargetEdge, int )								\
	LESSON_VARIABLE_MACRO( FIXED_POSITION_X, m_fFixedPositionX, float )								\
	LESSON_VARIABLE_MACRO( FIXED_POSITION_Y, m_fFixedPositionY, float )								\
	LESSON_VARIABLE_MACRO_BOOL( FIXED_POSITION, m_bFixedPosition, bool )							\
	LESSON_VARIABLE_MACRO_BOOL( NO_ICON_TARGET, m_bNoIconTarget, bool )								\
	LESSON_VARIABLE_MACRO_BOOL( ALLOW_NODRAW_TARGET, m_bAllowNodrawTarget, bool )					\
	LESSON_VARIABLE_MACRO_BOOL( VISIBLE, m_bVisible, bool )											\
	LESSON_VARIABLE_MACRO_BOOL( SHOW_WHEN_OCCLUDED, m_bShowWhenOccluded, bool )						\
	LESSON_VARIABLE_MACRO_BOOL( NO_OFFSCREEN, m_bNoOffscreen, bool )								\
	LESSON_VARIABLE_MACRO_BOOL( FORCE_CAPTION, m_bForceCaption, bool )								\
	LESSON_VARIABLE_MACRO_STRING( ONSCREEN_ICON, m_szOnscreenIcon, CGameInstructorSymbol )			\
	LESSON_VARIABLE_MACRO_STRING( OFFSCREEN_ICON, m_szOffscreenIcon, CGameInstructorSymbol )		\
	LESSON_VARIABLE_MACRO( ICON_OFFSET, m_flUpOffset, float )										\
	LESSON_VARIABLE_MACRO( ICON_RELATIVE_OFFSET, m_flRelativeUpOffset, float )						\
	LESSON_VARIABLE_MACRO( RANGE, m_fRange, float )													\
																									\
	LESSON_VARIABLE_MACRO( FLAGS, m_iFlags, int )											\
	LESSON_VARIABLE_MACRO_STRING( CAPTION_COLOR, m_szCaptionColor, CGameInstructorSymbol )	\
	LESSON_VARIABLE_MACRO_STRING( GROUP, m_szLessonGroup, CGameInstructorSymbol )			\
																							\
	LESSON_VARIABLE_MACRO_STRING( CAPTION, m_szDisplayText, CGameInstructorSymbol )				\
	LESSON_VARIABLE_MACRO_STRING( CAPTION_PARAM, m_szDisplayParamText, CGameInstructorSymbol )	\
	LESSON_VARIABLE_MACRO_STRING( BINDING, m_szBinding, CGameInstructorSymbol )					\
	LESSON_VARIABLE_MACRO_STRING( GAMEPAD_BINDING, m_szGamepadBinding, CGameInstructorSymbol )	\
																								\
	LESSON_VARIABLE_MACRO( PRIORITY, m_iPriority, int )										\
	LESSON_VARIABLE_MACRO_STRING( REPLACE_KEY, m_stringReplaceKey, CGameInstructorSymbol )	\
																							\
	LESSON_VARIABLE_MACRO( LOCK_DURATION, m_fLockDuration, float )										\
	LESSON_VARIABLE_MACRO_BOOL( CAN_OPEN_WHEN_DEAD, m_bCanOpenWhenDead, bool )							\
	LESSON_VARIABLE_MACRO_BOOL( BUMP_WITH_TIMEOUT_WHEN_LEARNED, m_bBumpWithTimeoutWhenLearned, bool )	\
	LESSON_VARIABLE_MACRO_BOOL( CAN_TIMEOUT_WHILE_INACTIVE, m_bCanTimeoutWhileInactive, bool )			\
	LESSON_VARIABLE_MACRO( TIMEOUT, m_fTimeout, float )													\
	LESSON_VARIABLE_MACRO( UPDATE_INTERVAL, m_fUpdateInterval, float )									\
	LESSON_VARIABLE_MACRO_STRING( START_SOUND, m_szStartSound, CGameInstructorSymbol )					\


// Create keyvalues name symbol
#define LESSON_VARIABLE_SYMBOL( _varEnum, _varName, _varType ) static int g_n##_varEnum##Symbol;

#define LESSON_VARIABLE_INIT_SYMBOL( _varEnum, _varName, _varType ) g_n##_varEnum##Symbol = KeyValuesSystem()->GetSymbolForString( #_varEnum );

#define LESSON_SCRIPT_STRING_ADD_TO_MAP( _varEnum, _varName, _varType ) g_NameToTypeMap.Insert( #_varEnum, LESSON_VARIABLE_##_varEnum## );

// Create enum value
#define LESSON_VARIABLE_ENUM( _varEnum, _varName, _varType ) LESSON_VARIABLE_##_varEnum##,

// Init info call
#define LESSON_VARIABLE_INIT_INFO_CALL( _varEnum, _varName, _varType ) g_pLessonVariableInfo[ LESSON_VARIABLE_##_varEnum## ].Init_##_varEnum##();

// Init info
#define LESSON_VARIABLE_INIT_INFO( _varEnum, _varName, _varType ) \
	void Init_##_varEnum##() \
	{ \
		iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
		varType = LessonParamTypeFromString( #_varType ); \
	}

#define LESSON_VARIABLE_INIT_INFO_BOOL( _varEnum, _varName, _varType ) \
	void Init_##_varEnum##() \
	{ \
		iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
		varType = FIELD_BOOLEAN; \
	}

#define LESSON_VARIABLE_INIT_INFO_EHANDLE( _varEnum, _varName, _varType ) \
	void Init_##_varEnum##() \
	{ \
		iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
		varType = FIELD_EHANDLE; \
	}

#define LESSON_VARIABLE_INIT_INFO_STRING( _varEnum, _varName, _varType ) \
	void Init_##_varEnum##() \
	{ \
		iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
		varType = FIELD_STRING; \
	}

// Copy defaults into this scripted lesson into a new one
#define LESSON_VARIABLE_DEFAULT( _varEnum, _varName, _varType ) ( _varName = m_pDefaultHolder->_varName );

// Copy a variable from this scripted lesson into a new one
#define LESSON_VARIABLE_COPY( _varEnum, _varName, _varType ) ( pOpenLesson->_varName = _varName );

// Return the first param if pchName is the same as the second param
#define LESSON_SCRIPT_STRING( _type, _string ) \
	if ( Q_stricmp( pchName, _string ) == 0 )\
	{\
		return _type;\
	}

// Wrapper for using this macro in the factory
#define LESSON_SCRIPT_STRING_GENERAL( _varEnum, _varName, _varType ) LESSON_SCRIPT_STRING( LESSON_VARIABLE_##_varEnum##, #_varEnum )

// Process the element action on this variable 
#define PROCESS_LESSON_ACTION( _varEnum, _varName, _varType ) \
	case LESSON_VARIABLE_##_varEnum##:\
		return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, _varName, &pLessonElement->szParam, eventParam_float );

#define PROCESS_LESSON_ACTION_EHANDLE( _varEnum, _varName, _varType ) \
	case LESSON_VARIABLE_##_varEnum##:\
		return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, _varName, &pLessonElement->szParam, eventParam_float, eventParam_BaseEntity, eventParam_string );

#define PROCESS_LESSON_ACTION_STRING( _varEnum, _varName, _varType ) \
	case LESSON_VARIABLE_##_varEnum##:\
		return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, &_varName, &pLessonElement->szParam, eventParam_string );

// Init the variable from the script (or a convar)
#define LESSON_VARIABLE_INIT( _varEnum, _varName, _varType ) \
	else if ( g_n##_varEnum##Symbol == pSubKey->GetNameSymbol() ) \
	{ \
		const char *pchParam = pSubKey->GetString(); \
		if ( pchParam && StringHasPrefix( pchParam, "convar " ) ) \
		{ \
			ConVarRef tempCVar( pchParam + Q_strlen( "convar " ) ); \
			if ( tempCVar.IsValid() ) \
			{ \
				_varName = static_cast<_varType>( tempCVar.GetFloat() ); \
			} \
			else \
			{ \
				_varName = static_cast<_varType>( 0.0f ); \
			} \
		} \
		else \
		{ \
			_varName = static_cast<_varType>( pSubKey->GetFloat() ); \
		} \
	}

#define LESSON_VARIABLE_INIT_BOOL( _varEnum, _varName, _varType ) \
	else if ( Q_stricmp( #_varEnum, pSubKey->GetName() ) == 0 ) \
	{ \
		_varName = pSubKey->GetBool(); \
	}

#define LESSON_VARIABLE_INIT_EHANDLE( _varEnum, _varName, _varType ) \
	else if ( g_n##_varEnum##Symbol == pSubKey->GetNameSymbol() ) \
	{ \
		DevWarning( "Can't initialize an EHANDLE from the instructor lesson script." ); \
	}

#define LESSON_VARIABLE_INIT_STRING( _varEnum, _varName, _varType ) \
	else if ( g_n##_varEnum##Symbol == pSubKey->GetNameSymbol() ) \
	{ \
		const char *pchParam = pSubKey->GetString(); \
		if ( pchParam && StringHasPrefix( pchParam, "convar " ) ) \
		{ \
			ConVarRef tempCVar( pchParam + Q_strlen( "convar " ) ); \
			if ( tempCVar.IsValid() ) \
			{ \
				_varName = tempCVar.GetString(); \
			} \
			else \
			{ \
				_varName = ""; \
			} \
		} \
		else \
		{ \
			_varName = pSubKey->GetString(); \
		} \
	}

// Gets a scripted variable by offset and casts it to the proper type
#define LESSON_VARIABLE_GET_FROM_OFFSET( _type, _offset ) *static_cast<_type*>( static_cast<void*>( static_cast<byte*>( static_cast<void*>( this ) ) + _offset ) )


// Enum of scripted variables
enum LessonVariable
{
	// Run enum macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_ENUM
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_ENUM
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_ENUM
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_ENUM
	LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING

	LESSON_VARIABLE_TOTAL
};

// Declare the keyvalues symbols for the keynames
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_SYMBOL
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_SYMBOL
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_SYMBOL
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_SYMBOL
	LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING

// String lookup prototypes
LessonVariable LessonVariableFromString( const char *pchName, bool bWarnOnInvalidNames = true );
_fieldtypes LessonParamTypeFromString( const char *pchName );
int LessonActionFromString( const char *pchName );


// This is used to get type info an variable offsets from the variable enumerated value
class LessonVariableInfo
{
public:

	LessonVariableInfo() 
		: iOffset( 0 ), varType( FIELD_VOID )
	{
	}

	// Run init info macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_INIT_INFO
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_INIT_INFO_BOOL
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_INIT_INFO_EHANDLE
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_INIT_INFO_STRING
	LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING

public:

	int iOffset;
	_fieldtypes varType;
};

LessonVariableInfo g_pLessonVariableInfo[ LESSON_VARIABLE_TOTAL ];


const LessonVariableInfo *GetLessonVariableInfo( int iLessonVariable )
{
	Assert( iLessonVariable >= 0 && iLessonVariable < LESSON_VARIABLE_TOTAL );

	if ( g_pLessonVariableInfo[ 0 ].varType == FIELD_VOID )
	{
		// Run init info call macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_INIT_INFO_CALL
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_INIT_INFO_CALL
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_INIT_INFO_CALL
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_INIT_INFO_CALL
		LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING
	}

	return &(g_pLessonVariableInfo[ iLessonVariable ]);
}

static CUtlDict< LessonVariable, int > g_NameToTypeMap;
static CUtlDict< fieldtype_t, int > g_TypeToParamTypeMap;
CUtlDict< int, int > CScriptedIconLesson::LessonActionMap;

CScriptedIconLesson::~CScriptedIconLesson()
{
	if ( m_pDefaultHolder )
	{
		delete m_pDefaultHolder;
		m_pDefaultHolder = NULL;
	}
}


void CScriptedIconLesson::Init()
{
	m_hLocalPlayer.Set( NULL );
	m_fOutput = 0.0f;
	m_hEntity1.Set( NULL );
	m_hEntity2.Set( NULL );
	m_szString1 = "";
	m_szString2 = "";
	m_iInteger1 = 0;
	m_iInteger2 = 0;
	m_fFloat1	= 0.0f;
	m_fFloat2	= 0.0f;

	m_fUpdateEventTime	= 0.0f;
	m_pDefaultHolder	= NULL;
	m_iScopeDepth		= 0;

	if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Initializing scripted lesson " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" );
	}

	if ( !IsDefaultHolder() )
	{
		if ( !IsOpenOpportunity() )
		{
			// Initialize from the key value file
			InitFromKeys( GetGameInstructor().GetScriptKeys() );

			if ( m_iPriority >= LESSON_PRIORITY_MAX )
			{
				DevWarning( "Priority level not set for lesson: %s\n", GetName() );
			}

			// We use this to remember variable defaults to be reset before each open attempt
			m_pDefaultHolder = new CScriptedIconLesson( GetName(), true, false );
			CScriptedIconLesson *pOpenLesson = m_pDefaultHolder;

			// Run copy macros on all default scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_COPY
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_COPY
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_COPY
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_COPY
			LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING

			// Listen for open events
			for ( int iLessonEvent = 0; iLessonEvent < m_OpenEvents.Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(m_OpenEvents[ iLessonEvent ]);
				ListenForGameEvent( pLessonEvent->szEventName.String() );

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tListen for open event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" );
				}
			}

			// Listen for close events
			for ( int iLessonEvent = 0; iLessonEvent < m_CloseEvents.Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(m_CloseEvents[ iLessonEvent ]);
				ListenForGameEvent( pLessonEvent->szEventName.String() );

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tListen for close event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\"", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" );
				}
			}

			// Listen for success events
			for ( int iLessonEvent = 0; iLessonEvent < m_SuccessEvents.Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(m_SuccessEvents[ iLessonEvent ]);
				ListenForGameEvent( pLessonEvent->szEventName.String());

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tListen for success event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\"", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" );
				}
			}
		}
		else
		{
			// This is an open lesson! Get the root for reference
			const CScriptedIconLesson *pLesson = static_cast<const CScriptedIconLesson *>( GetGameInstructor().GetLesson( GetName() ) );
			SetRoot( const_cast<CScriptedIconLesson *>( pLesson ) );
		}
	}
}

void CScriptedIconLesson::InitPrerequisites()
{
	if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Initializing prereqs for scripted lesson " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" );
	}

	for ( int iPrerequisit = 0; iPrerequisit < m_PrerequisiteNames.Count(); ++iPrerequisit )
	{
		const char *pPrerequisiteName = m_PrerequisiteNames[ iPrerequisit ].String();
		AddPrerequisite( pPrerequisiteName );
	}
}

void CScriptedIconLesson::OnOpen()
{
	VPROF_BUDGET( "CScriptedIconLesson::OnOpen", "GameInstructor" );

	if ( !DoDelayedPlayerSwaps() )
	{
		return;
	}

	const CScriptedIconLesson *pLesson = static_cast<const CScriptedIconLesson *>( GetRoot() );

	// Process all update events
	for ( int iLessonEvent = 0; iLessonEvent < pLesson->m_OnOpenEvents.Count(); ++iLessonEvent )
	{
		const LessonEvent_t *pLessonEvent = &(pLesson->m_OnOpenEvents[ iLessonEvent ]);

		if ( gameinstructor_verbose.GetInt() > 1 && ShouldShowSpew() )
		{
			ConColorMsg( Color( 255, 128, 64, 255 ), "GAME INSTRUCTOR: " );
			ConColorMsg( Color( 64, 128, 255, 255 ), "OnOpen event " );
			ConColorMsg( Color( 0, 255, 0, 255 ), "\"%s\"", pLessonEvent->szEventName.String());
			ConColorMsg( Color( 64, 128, 255, 255 ), "received for lesson \"%s\"...\n", GetName() );
		}

		ProcessElements( NULL, &(pLessonEvent->elements) );
	}

	BaseClass::OnOpen();
}

void CScriptedIconLesson::Update()
{
	VPROF_BUDGET( "CScriptedIconLesson::Update", "GameInstructor" );

	if ( !DoDelayedPlayerSwaps() )
	{
		return;
	}

	const CScriptedIconLesson *pLesson = static_cast<const CScriptedIconLesson *>( GetRoot() );

	if ( gpGlobals->curtime >= m_fUpdateEventTime )
	{
		bool bShowSpew = ( gameinstructor_verbose.GetInt() > 1 && ShouldShowSpew() );

		int iVerbose = gameinstructor_verbose.GetInt();
		if ( gameinstructor_verbose.GetInt() == 1 )
		{
			// Force the verbose level from 1 to 0 for update events
			gameinstructor_verbose.SetValue( 0 );
		}

		// Process all update events
		for ( int iLessonEvent = 0; iLessonEvent < pLesson->m_UpdateEvents.Count(); ++iLessonEvent )
		{
			const LessonEvent_t *pLessonEvent = &(pLesson->m_UpdateEvents[ iLessonEvent ]);

			if ( bShowSpew )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Update event " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseUpdate, "\"%s\"", pLessonEvent->szEventName.String());
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() );
			}

			ProcessElements( NULL, &(pLessonEvent->elements) );
		}

		gameinstructor_verbose.SetValue( iVerbose );

		// Wait before doing update events again
		m_fUpdateEventTime = gpGlobals->curtime + m_fUpdateInterval;
	}

	BaseClass::Update();
}

void CScriptedIconLesson::SwapOutPlayers( int iOldUserID, int iNewUserID )
{
	BaseClass::SwapOutPlayers( iOldUserID, iNewUserID );

	// Get the player pointers from the user IDs
	C_BasePlayer *pOldPlayer = UTIL_PlayerByUserId( iOldUserID );
	C_BasePlayer *pNewPlayer = UTIL_PlayerByUserId( iNewUserID );

	if ( pOldPlayer == m_hEntity1.Get() )
	{
		if ( pNewPlayer )
		{
			m_hEntity1 = pNewPlayer;
		}
		else
		{
			if ( m_iNumDelayedPlayerSwaps < MAX_DELAYED_PLAYER_SWAPS )
			{
				m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].phHandleToChange = &m_hEntity1;
				m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].iNewUserID = iNewUserID;
				++m_iNumDelayedPlayerSwaps;
			}
		}
	}

	if ( pOldPlayer == m_hEntity2.Get() )
	{
		if ( pNewPlayer )
		{
			m_hEntity2 = pNewPlayer;
		}
		else
		{

			if ( m_iNumDelayedPlayerSwaps < MAX_DELAYED_PLAYER_SWAPS )
			{
				m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].phHandleToChange = &m_hEntity2;
				m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].iNewUserID = iNewUserID;
				++m_iNumDelayedPlayerSwaps;
			}
		}
	}
}

void CScriptedIconLesson::FireGameEvent( IGameEvent *event )
{
	VPROF_BUDGET( "CScriptedIconLesson::FireGameEvent", "GameInstructor" );

	if ( m_bDisabled )
		return;

	if ( !DoDelayedPlayerSwaps() )
	{
		return;
	}

	if ( !C_BasePlayer::GetLocalPlayer() )
		return;

	// Check that this lesson is allowed for the current input device
	if( m_bOnlyKeyboard /*&& input->ControllerModeActive()*/ )
		return;

	if( m_bOnlyGamepad /*&& !input->ControllerModeActive()*/ )
		return;

	// Check that this lesson is for the proper team
	CBasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer();

	if ( m_iTeam != TEAM_ANY && pLocalPlayer && pLocalPlayer->GetTeamNumber() != m_iTeam )
	{
		// This lesson is intended for a different team
		return;
	}

	const char *name = event->GetName();

	// Open events run on the root
	ProcessOpenGameEvents( this, name, event );

	// Close and success events run on the children
	const CUtlVector < CBaseLesson * > *pChildren = GetChildren();
	for ( int iChild = 0; iChild < pChildren->Count(); ++iChild )
	{
		CScriptedIconLesson *pScriptedChild = dynamic_cast<CScriptedIconLesson*>( (*pChildren)[ iChild ] );
		
		pScriptedChild->ProcessCloseGameEvents( this, name, event );
		pScriptedChild->ProcessSuccessGameEvents( this, name, event );
	}
}

void CScriptedIconLesson::ProcessOpenGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event )
{
	if ( pRootLesson->InstanceType() == LESSON_INSTANCE_SINGLE_OPEN && GetGameInstructor().IsLessonOfSameTypeOpen( this ) )
	{
		// We don't want more than one of this type, and there is already one open
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
			ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pRootLesson->GetName() );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "open events NOT processed (there is already an open lesson of this type).\n" );
		}

		return;
	}

	for ( int iLessonEvent = 0; iLessonEvent < pRootLesson->m_OpenEvents.Count(); ++iLessonEvent )
	{
		const LessonEvent_t *pLessonEvent = &(pRootLesson->m_OpenEvents[ iLessonEvent ]);

		if ( Q_strcmp( name, pLessonEvent->szEventName.String()) == 0 )
		{
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Open event " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pLessonEvent->szEventName.String());
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() );
			}

			if ( m_pDefaultHolder )
			{
				// Run copy from default macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_DEFAULT
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_DEFAULT
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_DEFAULT
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_DEFAULT
				LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING
			}

			if ( ProcessElements( event, &(pLessonEvent->elements) ) )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\tAll elements returned true. Opening!\n" );
				}

				MEM_ALLOC_CREDIT();
				CScriptedIconLesson *pOpenLesson = new CScriptedIconLesson( GetName(), false, true );

				// Run copy macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_COPY
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_COPY
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_COPY
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_COPY
				LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING

				if ( GetGameInstructor().OpenOpportunity( pOpenLesson ) )
				{
					pOpenLesson->OnOpen();

					if ( pRootLesson->InstanceType() == LESSON_INSTANCE_SINGLE_OPEN )
					{
						// This one is open and we only want one! So, we're done.
						// Other open events may be listening for the same events... skip them!
						return;
					}
				}
			}
		}
	}
}

void CScriptedIconLesson::ProcessCloseGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event )
{
	for ( int iLessonEvent = 0; iLessonEvent < pRootLesson->m_CloseEvents.Count() && IsOpenOpportunity(); ++iLessonEvent )
	{
		const LessonEvent_t *pLessonEvent = &(pRootLesson->m_CloseEvents[ iLessonEvent ]);

		if ( Q_strcmp( name, pLessonEvent->szEventName.String()) == 0 )
		{
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Close event " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\"", pLessonEvent->szEventName.String());
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() );
			}

			if ( ProcessElements( event, &(pLessonEvent->elements) ) )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tAll elements returned true. Closing!\n" );
				}

				CloseOpportunity( "Close event elements completed." );
			}
		}
	}
}

void CScriptedIconLesson::ProcessSuccessGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event )
{
	for ( int iLessonEvent = 0; iLessonEvent < pRootLesson->m_SuccessEvents.Count(); ++iLessonEvent )
	{
		const LessonEvent_t *pLessonEvent = &(pRootLesson->m_SuccessEvents[ iLessonEvent ]);

		if ( Q_strcmp( name, pLessonEvent->szEventName.String()) == 0 )
		{
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Success event " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\"", pLessonEvent->szEventName.String());
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() );
			}

			if ( ProcessElements( event, &(pLessonEvent->elements) ) )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\tAll elements returned true. Succeeding!\n" );
				}

				MarkSucceeded();
			}
		}
	}
}

LessonVariable LessonVariableFromString( const char *pchName, bool bWarnOnInvalidNames )
{
	int slot = g_NameToTypeMap.Find( pchName );
	if ( slot != g_NameToTypeMap.InvalidIndex() )
		return g_NameToTypeMap[ slot ];

	if ( bWarnOnInvalidNames )
	{
		AssertMsg( 0, "Invalid scripted lesson variable!" );
		DevWarning( "Invalid scripted lesson variable: %s\n", pchName );
	}
	
	return LESSON_VARIABLE_TOTAL;
}

_fieldtypes LessonParamTypeFromString( const char *pchName )
{
	int slot = g_TypeToParamTypeMap.Find( pchName );
	if ( slot != g_TypeToParamTypeMap.InvalidIndex() )
		return g_TypeToParamTypeMap[ slot ];

	DevWarning( "Invalid scripted lesson variable/param type: %s\n", pchName );
	return FIELD_VOID;
}

int LessonActionFromString( const char *pchName )
{
	int slot = CScriptedIconLesson::LessonActionMap.Find( pchName );
	if ( slot != CScriptedIconLesson::LessonActionMap.InvalidIndex() )
		return CScriptedIconLesson::LessonActionMap[ slot ];

	DevWarning( "Invalid scripted lesson action: %s\n", pchName );
	return LESSON_ACTION_NONE;
}

void CScriptedIconLesson::InitElementsFromKeys( CUtlVector< LessonElement_t > *pLessonElements, KeyValues *pKey )
{
	KeyValues *pSubKey = NULL;
	for ( pSubKey = pKey->GetFirstSubKey(); pSubKey; pSubKey = pSubKey->GetNextKey() )
	{
		char szSubKeyName[ 256 ];
		Q_strcpy( szSubKeyName, pSubKey->GetName() );

		char *pchToken = strtok( szSubKeyName, " " );
		LessonVariable iVariable = LessonVariableFromString( pchToken );

		pchToken = strtok ( NULL, "" );
		int iAction = LESSON_ACTION_NONE;
		bool bNot = false;
		bool bOptionalParam = false;
		
		if ( !pchToken || pchToken[ 0 ] == '\0' )
		{
			DevWarning( "No action specified for variable: \"%s\"\n", pSubKey->GetName() );
		}
		else
		{
			if ( pchToken[ 0 ] == '?' )
			{
				pchToken++;
				bOptionalParam = true;
			}

			if ( pchToken[ 0 ] == '!' )
			{
				pchToken++;
				bNot = true;
			}
			
			iAction = LessonActionFromString( pchToken );
		}

		Q_strcpy( szSubKeyName, pSubKey->GetString() );

		pchToken = strtok( szSubKeyName, " " );
		_fieldtypes paramType = LessonParamTypeFromString( pchToken );

		char *pchParam = "";

		if ( paramType != FIELD_VOID )
		{
			pchToken = strtok ( NULL, "" );
			pchParam = pchToken;
		}

		if ( !pchParam )
		{
			DevWarning( "No parameter specified for action: \"%s\"\n", pSubKey->GetName() );
		}
		else
		{
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t\tElement \"%s %s\" added.\n", pSubKey->GetName(), pSubKey->GetString() );
			}

			// See if our param is a scripted var
			LessonVariable iParamVarIndex = LessonVariableFromString( pchParam, false );

			pLessonElements->AddToTail( LessonElement_t( iVariable, iAction, bNot, bOptionalParam, pchParam, iParamVarIndex, paramType ) );
		}
	}
}

void CScriptedIconLesson::InitElementsFromElements( CUtlVector< LessonElement_t > *pLessonElements, const CUtlVector< LessonElement_t > *pLessonElements2 )
{
	for ( int i = 0; i < pLessonElements2->Count(); ++i )
	{
		pLessonElements->AddToTail( LessonElement_t( (*pLessonElements2)[ i ] ) );
	}
}

void CScriptedIconLesson::InitFromKeys( KeyValues *pKey )
{
	if ( !pKey )
		return;

	static int s_nInstanceTypeSymbol = KeyValuesSystem()->GetSymbolForString( "instance_type" );
	static int s_nReplaceKeySymbol = KeyValuesSystem()->GetSymbolForString( "replace_key" );
	static int s_nFixedInstancesMaxSymbol = KeyValuesSystem()->GetSymbolForString( "fixed_instances_max" );
	static int s_nReplaceOnlyWhenStopped = KeyValuesSystem()->GetSymbolForString( "replace_only_when_stopped" );
	static int s_nTeamSymbol = KeyValuesSystem()->GetSymbolForString( "team" );
	static int s_nOnlyKeyboardSymbol = KeyValuesSystem()->GetSymbolForString( "only_keyboard" );
	static int s_nOnlyGamepadSymbol = KeyValuesSystem()->GetSymbolForString( "only_gamepad" );
	static int s_nDisplayLimitSymbol = KeyValuesSystem()->GetSymbolForString( "display_limit" );
	static int s_nSuccessLimitSymbol = KeyValuesSystem()->GetSymbolForString( "success_limit" );
	static int s_nPreReqSymbol = KeyValuesSystem()->GetSymbolForString( "prereq" );
	static int s_nOpenSymbol = KeyValuesSystem()->GetSymbolForString( "open" );
	static int s_nCloseSymbol = KeyValuesSystem()->GetSymbolForString( "close" );
	static int s_nSuccessSymbol = KeyValuesSystem()->GetSymbolForString( "success" );
	static int s_nOnOpenSymbol = KeyValuesSystem()->GetSymbolForString( "onopen" );
	static int s_nUpdateSymbol = KeyValuesSystem()->GetSymbolForString( "update" );

	KeyValues *pSubKey = NULL;
	for ( pSubKey = pKey->GetFirstSubKey(); pSubKey; pSubKey = pSubKey->GetNextKey() )
	{
		if ( pSubKey->GetNameSymbol() == s_nInstanceTypeSymbol )
		{
			m_iInstanceType = LessonInstanceType( pSubKey->GetInt() );
		}
		else if ( pSubKey->GetNameSymbol() == s_nReplaceKeySymbol )
		{
			m_stringReplaceKey = pSubKey->GetString();
		}
		else if ( pSubKey->GetNameSymbol() == s_nFixedInstancesMaxSymbol )
		{
			m_iFixedInstancesMax = pSubKey->GetInt();
		}
		else if ( pSubKey->GetNameSymbol() == s_nReplaceOnlyWhenStopped )
		{
			m_bReplaceOnlyWhenStopped = pSubKey->GetBool();
		}
		else if ( pSubKey->GetNameSymbol() == s_nTeamSymbol )
		{
			m_iTeam = pSubKey->GetInt();
		}
		else if ( pSubKey->GetNameSymbol() == s_nOnlyKeyboardSymbol )
		{
			m_bOnlyKeyboard = pSubKey->GetBool();
		}
		else if ( pSubKey->GetNameSymbol() == s_nOnlyGamepadSymbol )
		{
			m_bOnlyGamepad = pSubKey->GetBool();
		}
		else if ( pSubKey->GetNameSymbol() == s_nDisplayLimitSymbol )
		{
			m_iDisplayLimit = pSubKey->GetInt();
		}
		else if ( pSubKey->GetNameSymbol() == s_nSuccessLimitSymbol )
		{
			m_iSuccessLimit = pSubKey->GetInt();
		}
		else if ( pSubKey->GetNameSymbol() == s_nPreReqSymbol )
		{
			CGameInstructorSymbol pName;
			pName = pSubKey->GetString();
			m_PrerequisiteNames.AddToTail( pName );
		}
		else if ( pSubKey->GetNameSymbol() == s_nOpenSymbol )
		{
			KeyValues *pEventKey = NULL;
			for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() )
			{
				LessonEvent_t *pLessonEvent = AddOpenEvent();
				pLessonEvent->szEventName = pEventKey->GetName();

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding open event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" );
				}

				InitElementsFromKeys( &(pLessonEvent->elements), pEventKey );
			}
		}
		else if ( pSubKey->GetNameSymbol() == s_nCloseSymbol )
		{
			KeyValues *pEventKey = NULL;
			for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() )
			{
				LessonEvent_t *pLessonEvent = AddCloseEvent();
				pLessonEvent->szEventName = pEventKey->GetName();

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding close event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" );
				}

				InitElementsFromKeys( &(pLessonEvent->elements), pEventKey );
			}
		}
		else if ( pSubKey->GetNameSymbol() == s_nSuccessSymbol )
		{
			KeyValues *pEventKey = NULL;
			for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() )
			{
				LessonEvent_t *pLessonEvent = AddSuccessEvent();
				pLessonEvent->szEventName = pEventKey->GetName();

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding success event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\" ", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" );
				}

				InitElementsFromKeys( &(pLessonEvent->elements), pEventKey );
			}
		}
		else if ( pSubKey->GetNameSymbol() == s_nOnOpenSymbol )
		{
			KeyValues *pEventKey = NULL;
			for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() )
			{
				LessonEvent_t *pLessonEvent = AddOnOpenEvent();
				pLessonEvent->szEventName = pEventKey->GetName();

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding onopen event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" );
				}

				InitElementsFromKeys( &(pLessonEvent->elements), pEventKey );
			}
		}
		else if ( pSubKey->GetNameSymbol() == s_nUpdateSymbol )
		{
			KeyValues *pEventKey = NULL;
			for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() )
			{
				LessonEvent_t *pLessonEvent = AddUpdateEvent();
				pLessonEvent->szEventName = pEventKey->GetName();

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding update event " );
					ConColorMsg( CBaseLesson::m_rgbaVerboseUpdate, "\"%s\" ", pLessonEvent->szEventName.String());
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" );
				}

				InitElementsFromKeys( &(pLessonEvent->elements), pEventKey );
			}
		}

		// Run intialize from key macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_INIT
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_INIT_BOOL
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_INIT_EHANDLE
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_INIT_STRING
		LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING
	}
}

bool CScriptedIconLesson::ProcessElements( IGameEvent *event, const CUtlVector< LessonElement_t > *pElements )
{
	VPROF_BUDGET( "CScriptedIconLesson::ProcessElements", "GameInstructor" );

	m_hLocalPlayer = GetGameInstructor().GetLocalPlayer();

	bool bSuccess = true;
	int nContinueScope = -1;
	m_iScopeDepth = 0;

	if ( gameinstructor_find_errors.GetBool() )
	{
		// Just run them all to check for errors!
		for ( int iElement = 0; iElement < pElements->Count(); ++iElement )
		{
			ProcessElement( event, &((*pElements)[ iElement ] ), false );
		}

		return false;
	}

	// Process each element until a step fails
	for ( int iElement = 0; iElement < pElements->Count(); ++iElement )
	{
		if ( nContinueScope == m_iScopeDepth )
		{
			nContinueScope = -1;
		}

		if ( !ProcessElement( event, &((*pElements)[ iElement ]), nContinueScope != -1 ) )
		{
			// This element failed
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tPrevious element returned false.\n" );
			}

			nContinueScope = m_iScopeDepth - 1;

			if ( nContinueScope < 0 )
			{
				// No outer scope to worry about, we're done
				bSuccess = false;
				break;
			}
		}
	}

	return bSuccess;
}

bool CScriptedIconLesson::ProcessElement( IGameEvent *event, const LessonElement_t *pLessonElement, bool bInFailedScope )
{
	VPROF_BUDGET( "CScriptedIconLesson::ProcessElement", "GameInstructor" );

	if ( pLessonElement->iAction == LESSON_ACTION_SCOPE_IN )
	{
		// Special case for closing (we don't need variables for this)
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tScopeIn()\n" );
		}

		m_iScopeDepth++;
		return true;
	}
	else if ( pLessonElement->iAction == LESSON_ACTION_SCOPE_OUT )
	{
		// Special case for closing (we don't need variables for this)
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tScopeOut()\n" );
		}

		m_iScopeDepth--;
		return true;
	}

	if ( bInFailedScope )
	{
		// Only scope upkeep is done when we're in a failing scope... bail!
		return true;
	}

	if ( pLessonElement->iAction == LESSON_ACTION_CLOSE )
	{
		// Special case for closing (we don't need variables for this)
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tCloseOpportunity()\n" );
		}

		CloseOpportunity( "Close action." );
		return true;
	}
	else if ( pLessonElement->iAction == LESSON_ACTION_SUCCESS )
	{
		// Special case for succeeding (we don't need variables for this)
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tMarkSucceeded()\n" );
		}

		MarkSucceeded();
		return true;
	}
	else if ( pLessonElement->iAction == LESSON_ACTION_LOCK )
	{
		// Special case for setting the starting point for the lesson to stay locked from (we don't need variables for this)
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tm_fLockTime = gpGlobals->curtime\n" );
		}

		m_fLockTime = gpGlobals->curtime;
		return true;
	}
	else if ( pLessonElement->iAction == LESSON_ACTION_PRESENT_COMPLETE )
	{
		// Special case for checking presentation status (we don't need variables for this)
		bool bPresentComplete = IsPresentComplete();

		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tIsPresentComplete() " );
			ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%s ", ( bPresentComplete ) ? ( "true" ) : ( "false" ) );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( pLessonElement->bNot ) ? ( "!= true\n" ) : ( "== true\n" ) );
		}

		return ( pLessonElement->bNot ) ? ( !bPresentComplete ) : ( bPresentComplete );
	}
	else if ( pLessonElement->iAction == LESSON_ACTION_PRESENT_START )
	{
		// Special case for setting presentation status (we don't need variables for this)
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPresentStart()\n" );
		}

		PresentStart();
		return true;
	}
	else if ( pLessonElement->iAction == LESSON_ACTION_PRESENT_END )
	{
		// Special case for setting presentation status (we don't need variables for this)
		if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPresentEnd()\n" );
		}

		PresentEnd();
		return true;
	}

	// These values temporarily hold the parameter's value
	const char *pParamName = pLessonElement->szParam.String();
	float eventParam_float = 0.0f;
	char eventParam_string[ 256 ];
	eventParam_string[ 0 ] = '\0';
	C_BaseEntity *eventParam_BaseEntity = NULL;

	// Get the value from the event parameter based on its type
	switch ( pLessonElement->paramType )
	{
	case FIELD_FLOAT:
		if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL )
		{
			// The parameter is a scripted var
			const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex );

			switch ( pInfo->varType )
			{
			case FIELD_FLOAT:
				eventParam_float = LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset );
				break;
			case FIELD_INTEGER:
				eventParam_float = static_cast<float>( LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset ) );
				break;
			case FIELD_BOOLEAN:
				eventParam_float = static_cast<float>( LESSON_VARIABLE_GET_FROM_OFFSET( bool, pInfo->iOffset ) );
				break;
			case FIELD_STRING:
				eventParam_float = static_cast<float>( atoi( &LESSON_VARIABLE_GET_FROM_OFFSET( CGameInstructorSymbol, pInfo->iOffset )->String() ) );
				break;
			case FIELD_EHANDLE:
			case FIELD_FUNCTION:
				DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" );
				break;
			}
		}
		else if ( event && !(event->IsEmpty( pParamName )) )
		{
			eventParam_float = event->GetFloat( pParamName );
		}
		else if ( pLessonElement->bOptionalParam )
		{
			// We don't want to interpret this and not finding the param is still ok
			return true;
		}
		else if ( ( pParamName[ 0 ] >= '0' && pParamName[ 0 ] <= '9' ) || pParamName[ 0 ] == '-' || pParamName[ 0 ] == '.' )
		{
			// This param doesn't exist, try parsing the string
			eventParam_float = Q_atof( pParamName );
		}
		else
		{
			DevWarning( "Invalid event field name and not a float \"%s\".\n", pParamName );
			return false;
		}
		break;

	case FIELD_INTEGER:
		if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL )
		{
			// The parameter is a scripted var
			const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex );

			switch ( pInfo->varType )
			{
			case FIELD_FLOAT:
				eventParam_float = static_cast<int>( LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset ) );
				break;
			case FIELD_INTEGER:
				eventParam_float = LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset );
				break;
			case FIELD_BOOLEAN:
				eventParam_float = static_cast<int>( LESSON_VARIABLE_GET_FROM_OFFSET( bool, pInfo->iOffset ) );
				break;
			case FIELD_STRING:
				eventParam_float = atof( &LESSON_VARIABLE_GET_FROM_OFFSET( CGameInstructorSymbol, pInfo->iOffset )->String() );
				break;
			case FIELD_EHANDLE:
			case FIELD_FUNCTION:
				DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" );
				break;
			}
		}
		else if ( event && !(event->IsEmpty( pParamName )) )
		{
			eventParam_float = static_cast<float>( event->GetInt( pParamName ) );
		}
		else if ( pLessonElement->bOptionalParam )
		{
			// We don't want to interpret this and not finding the param is still ok
			return true;
		}
		else if ( ( pParamName[ 0 ] >= '0' && pParamName[ 0 ] <= '9' ) || pParamName[ 0 ] == '-' )
		{
			// This param doesn't exist, try parsing the string
			eventParam_float = static_cast<float>( Q_atoi( pParamName ) );
		}
		else
		{
			DevWarning( "Invalid event field name and not an integer \"%s\".\n", pParamName );
			return false;
		}
		break;

	case FIELD_STRING:
		if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL )
		{
			// The parameter is a scripted var
			const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex );

			switch ( pInfo->varType )
			{
			case FIELD_STRING:
				Q_strncpy( eventParam_string, &LESSON_VARIABLE_GET_FROM_OFFSET( CGameInstructorSymbol, pInfo->iOffset )->String(), sizeof( eventParam_string ) );
				break;
			case FIELD_FLOAT:
				Q_snprintf( eventParam_string, sizeof( eventParam_string ), "%f", LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset ) );
				break;
			case FIELD_INTEGER:
				Q_snprintf( eventParam_string, sizeof( eventParam_string ), "%i", LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset ) );
				break;
			case FIELD_BOOLEAN:
			case FIELD_EHANDLE:
			case FIELD_FUNCTION:
				DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" );
				break;
			}
		}
		else
		{
			const char *pchEventString = NULL;

			if ( event && !(event->IsEmpty( pParamName )) )
			{
				pchEventString = event->GetString( pParamName );
			}

			if ( pchEventString && pchEventString[0] )
			{
				Q_strcpy( eventParam_string, pchEventString );
			}
			else if ( pLessonElement->bOptionalParam )
			{
				// We don't want to interpret this and not finding the param is still ok
				return true;
			}
			else
			{
				// This param doesn't exist, try parsing the string
				Q_strncpy( eventParam_string, pParamName, sizeof( eventParam_string ) );
			}
		}
		break;

	case FIELD_BOOLEAN:
		if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL )
		{
			// The parameter is a scripted var
			const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex );

			switch ( pInfo->varType )
			{
			case FIELD_FLOAT:
				eventParam_float = ( ( LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset ) ) ? ( 1.0f ) : ( 0.0f ) );
				break;
			case FIELD_INTEGER:
				eventParam_float = ( ( LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset ) ) ? ( 1.0f ) : ( 0.0f ) );
				break;
			case FIELD_BOOLEAN:
				eventParam_float = ( ( LESSON_VARIABLE_GET_FROM_OFFSET( bool, pInfo->iOffset ) ) ? ( 1.0f ) : ( 0.0f ) );
				break;
			case FIELD_EHANDLE:
			case FIELD_STRING:
			case FIELD_FUNCTION:
				DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" );
				break;
			}
		}
		else if ( event && !(event->IsEmpty( pParamName )) )
		{
			eventParam_float = ( event->GetBool( pParamName ) ) ? ( 1.0f ) : ( 0.0f );
		}
		else if ( pLessonElement->bOptionalParam )
		{
			// We don't want to interpret this and not finding the param is still ok
			return true;
		}
		else if ( pParamName[ 0 ] == '0' || pParamName[ 0 ] == '1' )
		{
			// This param doesn't exist, try parsing the string
			eventParam_float = Q_atof( pParamName ) != 0.0f;
		}
		else
		{
			DevWarning( "Invalid event field name and not an boolean \"%s\".\n", pParamName );
			return false;
		}
		break;

	case FIELD_CUSTOM:
		if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL )
		{
			// The parameter is a scripted var
			const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex );

			switch ( pInfo->varType )
			{
			case FIELD_EHANDLE:
				eventParam_BaseEntity = ( LESSON_VARIABLE_GET_FROM_OFFSET( EHANDLE, pInfo->iOffset ) ).Get();
				if ( !eventParam_BaseEntity )
				{
					if ( pLessonElement->bOptionalParam )
					{
						// Not having an entity is fine
						return true;
					}

					if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
					{
						ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPlayer param \"%s\" returned NULL.\n", pParamName );
					}
					return false;
				}
				break;
			case FIELD_FLOAT:
			case FIELD_INTEGER:
			case FIELD_BOOLEAN:
			case FIELD_STRING:
			case FIELD_FUNCTION:
				DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" );
				break;
			}
		}
		else if ( event && !(event->IsEmpty( pParamName )) )
		{
			eventParam_BaseEntity = UTIL_PlayerByUserId( event->GetInt( pParamName ) );
			if ( !eventParam_BaseEntity )
			{
				if ( pLessonElement->bOptionalParam )
				{
					// Not having an entity is fine
					return true;
				}

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPlayer param \"%s\" returned NULL.\n", pParamName );
				}
				return false;
			}
		}
		else if ( pLessonElement->bOptionalParam )
		{
			// We don't want to interpret this and not finding the param is still ok
			return true;
		}
		else if ( Q_stricmp( pParamName, "null" ) == 0 )
		{
			// They explicitly want a null pointer
			eventParam_BaseEntity = NULL;
		}
		else
		{
			DevWarning( "Invalid event field name \"%s\".\n", pParamName );
			return false;
		}
		break;

	case FIELD_EHANDLE:
		if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL )
		{
			// The parameter is a scripted var
			const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex );

			switch ( pInfo->varType )
			{
			case FIELD_EHANDLE:
				eventParam_BaseEntity = ( LESSON_VARIABLE_GET_FROM_OFFSET( EHANDLE, pInfo->iOffset ) ).Get();
				if ( !eventParam_BaseEntity )
				{
					if ( pLessonElement->bOptionalParam )
					{
						// Not having an entity is fine
						return true;
					}

					if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
					{
						ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tEntity param \"%s\" returned NULL.\n", pParamName );
					}
					return false;
				}
				break;
			case FIELD_FLOAT:
			case FIELD_INTEGER:
			case FIELD_BOOLEAN:
			case FIELD_STRING:
			case FIELD_FUNCTION:
				DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" );
				break;
			}
		}
		else if ( event && !(event->IsEmpty( pParamName ))  )
		{
			int iEntID = event->GetInt( pParamName );
			if ( iEntID >= NUM_ENT_ENTRIES )
			{
				AssertMsg( 0, "Invalid entity ID used in game event field!" );
				DevWarning( "Invalid entity ID used in game event (%s) for param (%s).", event->GetName(), pParamName );
				return false;
			}

			eventParam_BaseEntity = C_BaseEntity::Instance( iEntID );
			if ( !eventParam_BaseEntity )
			{
				if ( pLessonElement->bOptionalParam )
				{
					// Not having an entity is fine
					return true;
				}

				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tEntity param \"%s\" returned NULL.\n", pParamName );
				}
				return false;
			}
		}
		else if ( pLessonElement->bOptionalParam )
		{
			// We don't want to interpret this and not finding the param is still ok
			return true;
		}
		else if ( Q_stricmp( pParamName, "null" ) == 0 )
		{
			// They explicitly want a null pointer
			eventParam_BaseEntity = NULL;
		}
		else if ( Q_stricmp( pParamName, "world" ) == 0 )
		{
			// They explicitly want the world
			eventParam_BaseEntity = GetClientWorldEntity();
		}
		else
		{
			DevWarning( "Invalid event field name \"%s\".\n", pParamName );
			return false;
		}
		break;

	case FIELD_EMBEDDED:
		{
			// The parameter is a convar
			ConVarRef tempCVar( pParamName );
			if ( tempCVar.IsValid() )
			{
				eventParam_float = tempCVar.GetFloat();
				Q_strncpy( eventParam_string, tempCVar.GetString(), sizeof( eventParam_string ) );
			}
			else
			{
				DevWarning( "Invalid convar name \"%s\".\n", pParamName );
				return false;
			}
		}
		break;
	}

	// Do the action to the specified variable
	switch ( pLessonElement->iVariable )
	{
		// Run process action macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			PROCESS_LESSON_ACTION
#define LESSON_VARIABLE_MACRO_BOOL		PROCESS_LESSON_ACTION
#define LESSON_VARIABLE_MACRO_EHANDLE	PROCESS_LESSON_ACTION_EHANDLE
#define LESSON_VARIABLE_MACRO_STRING	PROCESS_LESSON_ACTION_STRING
		LESSON_VARIABLE_FACTORY;
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING
	}

	return true;
}

bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, float &fVar, const CGameInstructorSymbol *pchParamName, float fParam )
{
	switch ( iAction )
	{
		case LESSON_ACTION_SET:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
			}

			fVar = fParam;
			return true;

		case LESSON_ACTION_ADD:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] += [%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
			}

			fVar += fParam;
			return true;

		case LESSON_ACTION_SUBTRACT:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] -= [%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
			}

			fVar -= fParam;
			return true;

		case LESSON_ACTION_MULTIPLY:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] *= [%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
			}

			fVar *= fParam;
			return true;

		case LESSON_ACTION_IS:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f ", fVar );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
			}

			return ( bNot ) ? ( fVar != fParam ) : ( fVar == fParam );

		case LESSON_ACTION_LESS_THAN:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f ", fVar );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
			}

			return ( bNot ) ? ( fVar >= fParam ) : ( fVar < fParam );

		case LESSON_ACTION_HAS_BIT:
		{
			int iTemp1 = static_cast<int>( fVar );
			int iTemp2 = ( 1 << static_cast<int>( fParam ) );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t([%s] ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "0x%X ", iTemp1 );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "& [%s] ", pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "0x%X", iTemp2 );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") == 0\n" ) : ( ") != 0\n" ) );
			}

			return ( bNot ) ? ( ( iTemp1 & iTemp2 ) == 0 ) : ( ( iTemp1 & iTemp2 ) != 0 );
		}

		case LESSON_ACTION_BIT_COUNT_IS:
		{
			int iTemp1 = UTIL_CountNumBitsSet( static_cast<unsigned int>( fVar ) );
			int iTemp2 = static_cast<int>( fParam );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tUTIL_CountNumBitsSet([%s]) ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp1 );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( " != [%s] " ) : ( " == [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp2 );
			}

			return ( bNot ) ? ( iTemp1 != iTemp2 ) : ( iTemp1 == iTemp2 );
		}

		case LESSON_ACTION_BIT_COUNT_LESS_THAN:
		{
			int iTemp1 = UTIL_CountNumBitsSet( static_cast<unsigned int>( fVar ) );
			int iTemp2 = static_cast<int>( fParam );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tUTIL_CountNumBitsSet([%s]) ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp1 );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( " >= [%s] " ) : ( " < [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp2 );
			}

			return ( bNot ) ? ( iTemp1 >= iTemp2 ) : ( iTemp1 < iTemp2 );
		}
	}

	DevWarning( "Invalid lesson action type used with \"%s\" variable type.\n", pchVarName );

	return false;
}

bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, int &iVar, const CGameInstructorSymbol *pchParamName, float fParam )
{
	float fTemp = static_cast<float>( iVar );
	bool bRetVal = ProcessElementAction( iAction, bNot, pchVarName, fTemp, pchParamName, fParam );

	iVar = static_cast<int>( fTemp );
	return bRetVal;
}

bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, bool &bVar, const CGameInstructorSymbol *pchParamName, float fParam )
{
	float fTemp = ( bVar ) ? ( 1.0f ) : ( 0.0f );
	bool bRetVal = ProcessElementAction( iAction, bNot, pchVarName, fTemp, pchParamName, fParam );

	bVar = ( fTemp != 0.0f );
	return bRetVal;
}

bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, EHANDLE &hVar, const CGameInstructorSymbol *pchParamName, float fParam, C_BaseEntity *pParam, const char *pchParam )
{
	// First try to let the mod act on the action
	/*bool bModHandled = false;
	bool bModReturn = Mod_ProcessElementAction( iAction, bNot, pchVarName, hVar, pchParamName, fParam, pParam, pchParam, bModHandled );

	if ( bModHandled )
	{
		return bModReturn;
	}*/

	C_BaseEntity *pVar = hVar.Get();

	switch ( iAction )
	{
		case LESSON_ACTION_SET:
		{
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s]\n", pchVarName, pchParamName->String() );
			}

			hVar = pParam;
			return true;
		}

		case LESSON_ACTION_IS:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t[%s] != [%s]\n" ) : ( "\t[%s] == [%s]\n" ), pchVarName, pchParamName->String() );
			}

			return ( bNot ) ? ( pVar != pParam ) : ( pVar == pParam );

		case LESSON_ACTION_GET_DISTANCE:
		{
			if ( !pVar || !pParam )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->DistTo( [%s] )", pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "...\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle or Param handle returned NULL!\n" );
				}

				return false;
			}
			
			C_BasePlayer *pVarPlayer = ( pVar->IsPlayer() ? static_cast< C_BasePlayer* >( pVar ) : NULL );
			C_BasePlayer *pParamPlayer = ( pParam->IsPlayer() ? static_cast< C_BasePlayer* >( pParam ) : NULL );

			Vector vVarPos = ( pVarPlayer ? pVarPlayer->EyePosition() : pVar->WorldSpaceCenter() );
			Vector vParamPos = ( pParamPlayer ? pParamPlayer->EyePosition() : pParam->WorldSpaceCenter() );

			m_fOutput = vVarPos.DistTo( vParamPos );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->DistTo( [%s] ) ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput );
			}

			return true;
		}

		case LESSON_ACTION_GET_ANGULAR_DISTANCE:
		{
			if ( !pVar || !pParam )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->AngularDistTo( [%s] )", pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "...\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle or Param handle returned NULL!\n" );
				}

				return false;
			}

			C_BasePlayer *pVarPlayer = ( pVar->IsPlayer() ? static_cast< C_BasePlayer* >( pVar ) : NULL );
			C_BasePlayer *pParamPlayer = ( pParam->IsPlayer() ? static_cast< C_BasePlayer* >( pParam ) : NULL );

			Vector vVarPos = ( pVarPlayer ? pVarPlayer->EyePosition() : pVar->WorldSpaceCenter() );
			Vector vParamPos = ( pParamPlayer ? pParamPlayer->EyePosition() : pParam->WorldSpaceCenter() );

			Vector vVarToParam = vParamPos - vVarPos;
			VectorNormalize( vVarToParam );

			Vector vVarForward;

			if ( pVar->IsPlayer() )
			{
				AngleVectors( static_cast< C_BasePlayer* >( pVar )->EyeAngles(), &vVarForward, NULL, NULL );
			}
			else
			{
				pVar->GetVectors( &vVarForward, NULL, NULL );
			}

			// Set the distance in degrees
			m_fOutput = ( vVarToParam.Dot( vVarForward ) - 1.0f ) * -90.0f;

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->AngularDistTo( [%s] ) ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput );
			}

			return true;
		}

		case LESSON_ACTION_GET_PLAYER_DISPLAY_NAME:
		{
			int iTemp = static_cast<int>( fParam );

			if ( iTemp <= 0 || iTemp > 2 )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy( [stringINVALID], [%s]->GetPlayerName() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" );
				}

				return false;
			}

			// Use string2 if it was specified, otherwise, use string1
			CGameInstructorSymbol *pString;
			char const *pchParamNameTemp = NULL;

			if ( iTemp == 2 )
			{
				pString = &m_szString2;
				pchParamNameTemp = "string2";
			}
			else
			{
				pString = &m_szString1;
				pchParamNameTemp = "string1";
			}

			if ( !pVar )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy( [%s], [%s]->GetPlayerName() ", pchParamNameTemp, pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" );
				}

				return false;
			}

			*pString = pVar->GetPlayerName();

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy( [%s], [%s]->GetPlayerName() ", pchParamNameTemp, pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pString->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
			}

			return true;
		}

		case LESSON_ACTION_CLASSNAME_IS:
		{
			if ( !pVar )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t!FClassnameIs( [%s] " ) : ( "\tFClassnameIs( [%s] " ), pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "..." );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String()  );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );

					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" );
				}

				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t!FClassnameIs( [%s] " ) : ( "\tFClassnameIs( [%s] " ), pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%s", pVar->GetClassname() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String()  );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
			}

			return ( bNot ) ? ( !FClassnameIs( pVar, pchParam ) ) : ( FClassnameIs( pVar, pchParam ) );
		}

		case LESSON_ACTION_TEAM_IS:
		{
			int iTemp = static_cast<int>( fParam );

			if ( !pVar )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetTeamNumber() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" );
				}

				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetTeamNumber() ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", pVar->GetTeamNumber() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp );
			}

			return ( bNot ) ? ( pVar->GetTeamNumber() != iTemp ) : ( pVar->GetTeamNumber() == iTemp );
		}

		case LESSON_ACTION_MODELNAME_IS:
		{
			C_BaseAnimating *pBaseAnimating = dynamic_cast<C_BaseAnimating *>( pVar );

			if ( !pBaseAnimating )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_stricmp( [%s]->ModelName() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "..." );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String()  );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") != 0\n" ) : ( ") == 0\n" ) );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseAnimating returned NULL!\n" );
				}

				return false;
			}

			const char *pchModelName = "-no model-";
			CStudioHdr *pModel = pBaseAnimating->GetModelPtr();
			if ( pModel )
			{
				const studiohdr_t *pRenderHDR = pModel->GetRenderHdr();
				if ( pRenderHDR )
				{
					pchModelName = pRenderHDR->name;
				}
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_stricmp( [%s]->ModelName() ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%s", pchModelName );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String()  );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") != 0\n" ) : ( ") == 0\n" ) );
			}

			return ( bNot ) ? ( Q_stricmp( pchModelName, pchParam ) != 0 ) : ( Q_stricmp( pchModelName, pchParam ) == 0 );
		}
		
		case LESSON_ACTION_HEALTH_LESS_THAN:
		{
			int iTemp = static_cast<int>( fParam );

			if ( !pVar )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetHealth() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" );
				}

				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetHealth() ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", pVar->GetHealth() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp );
			}

			return ( bNot ) ? ( pVar->GetHealth() >= iTemp ) : ( pVar->GetHealth() < iTemp );
		}

		case LESSON_ACTION_HEALTH_PERCENTAGE_LESS_THAN:
		{
			if ( !pVar )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName, pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" );
				}

				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName, pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f ", pVar->HealthFraction() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
			}

			float fHealthPercentage = 1.0f;
			
			if ( pVar->GetMaxHealth() != 0.0f )
			{
				fHealthPercentage = pVar->HealthFraction();
			}

			return ( bNot ) ? ( fHealthPercentage >= fParam ) : ( fHealthPercentage < fParam );
		}

		case LESSON_ACTION_GET_ACTIVE_WEAPON:
		{
			int iTemp = static_cast<int>( fParam );

			if ( iTemp <= 0 || iTemp > 2 )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entityINVALID] = [%s]->GetActiveWeapon()\n", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" );
				}

				return false;
			}

			// Use entity2 if it was specified, otherwise, use entity1
			CHandle<C_BaseEntity> *pHandle;

			char const *pchParamNameTemp = NULL;

			if ( iTemp == 2 )
			{
				pHandle = &m_hEntity2;
				pchParamNameTemp = "entity2";
			}
			else
			{
				pHandle = &m_hEntity1;
				pchParamNameTemp = "entity1";
			}

			C_BaseCombatCharacter *pBaseCombatCharacter = NULL;

			if ( pVar )
			{
				pBaseCombatCharacter = pVar->MyCombatCharacterPointer();
			}

			if ( !pBaseCombatCharacter )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s]->GetActiveWeapon()", pchParamNameTemp, pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" );
				}

				return false;
			}

			pHandle->Set( pBaseCombatCharacter->GetActiveWeapon() );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s]->GetActiveWeapon()", pchParamNameTemp, pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam );
			}

			return true;
		}

		case LESSON_ACTION_WEAPON_IS:
		{
			C_BaseCombatCharacter *pBaseCombatCharacter = NULL;

			if ( pVar )
			{
				pBaseCombatCharacter = pVar->MyCombatCharacterPointer();
			}

			if ( !pBaseCombatCharacter )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->GetName() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" );
				}

				return false;
			}

			CBaseCombatWeapon *pBaseCombatWeapon = pBaseCombatCharacter->GetActiveWeapon();

			if ( !pBaseCombatWeapon )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->GetName() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" );
				}

				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->GetName() ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pBaseCombatWeapon->GetName() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam );
			}

			return ( bNot ) ? ( Q_stricmp( pBaseCombatWeapon->GetName(), pchParam ) != 0 ) : ( Q_stricmp( pBaseCombatWeapon->GetName(), pchParam ) == 0 );
		}

		case LESSON_ACTION_WEAPON_HAS:
		{
			C_BaseCombatCharacter *pBaseCombatCharacter = NULL;

			if ( pVar )
			{
				pBaseCombatCharacter = pVar->MyCombatCharacterPointer();
			}

			if ( !pBaseCombatCharacter )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_OwnsThisType([%s] " ) : ( "\t[%s]->Weapon_OwnsThisType([%s] " ), pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" );
				}

				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_OwnsThisType([%s] " ) : ( "\t[%s]->Weapon_OwnsThisType([%s] " ), pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
			}

			return ( bNot ) ? ( pBaseCombatCharacter->Weapon_OwnsThisType( pchParam ) == NULL ) : ( pBaseCombatCharacter->Weapon_OwnsThisType( pchParam ) != NULL );
		}

		case LESSON_ACTION_GET_ACTIVE_WEAPON_SLOT:
		{
			C_BaseCombatCharacter *pBaseCombatCharacter = NULL;

			if ( pVar )
			{
				pBaseCombatCharacter = pVar->MyCombatCharacterPointer();
			}

			if ( !pBaseCombatCharacter )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetActiveSlot() ...\n", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" );
				}

				return false;
			}

			C_BaseCombatWeapon *pWeapon = pBaseCombatCharacter->GetActiveWeapon();

			if ( !pWeapon )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetActiveSlot() ...\n", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" );
				}

				return false;
			}

			// @TODO
			/*m_fOutput = pBaseCombatCharacter->Weapon_GetSlot( pWeapon->GetWpnData().szClassName );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetSlot() ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput );
			}*/

			return true;
		}

		case LESSON_ACTION_GET_WEAPON_SLOT:
		{
			C_BaseCombatCharacter *pBaseCombatCharacter = NULL;

			if ( pVar )
			{
				pBaseCombatCharacter = pVar->MyCombatCharacterPointer();
			}

			if ( !pBaseCombatCharacter )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetSlot([%s] ", pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ") ...\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" );
				}

				return false;
			}

			// @TODO
			/*m_fOutput = pBaseCombatCharacter->Weapon_GetSlot( pchParam );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetSlot([%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ") " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput );
			}*/

			return true;
		}

		case LESSON_ACTION_GET_WEAPON_IN_SLOT:
		{
			int nTemp = static_cast<int>( fParam );

			C_BaseCombatCharacter *pBaseCombatCharacter = NULL;

			if ( pVar )
			{
				pBaseCombatCharacter = pVar->MyCombatCharacterPointer();
			}

			if ( !pBaseCombatCharacter )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entity1] = [%s]->GetWeapon([%s] ", pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%i\"", nTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" );
				}

				return false;
			}

			m_hEntity1 = pBaseCombatCharacter->GetWeapon( nTemp );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entity1] = [%s]->GetWeapon([%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%i\"", nTemp );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
			}

			return true;
		}

		case LESSON_ACTION_CLIP_PERCENTAGE_LESS_THAN:
		{
			C_BaseCombatCharacter *pBaseCombatCharacter = NULL;

			if ( pVar )
			{
				pBaseCombatCharacter = pVar->MyCombatCharacterPointer();
			}

			if ( !pBaseCombatCharacter )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->Clip1Percentage() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f\n", fParam );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" );
				}

				return false;
			}

			CBaseCombatWeapon *pBaseCombatWeapon = pBaseCombatCharacter->GetActiveWeapon();

			if ( !pBaseCombatWeapon )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->Clip1Percentage() ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f\n", fParam );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" );
				}

				return false;
			}

			float fClip1Percentage = 100.0f;

			if ( pBaseCombatWeapon->UsesClipsForAmmo1() )
			{
				fClip1Percentage = 100.0f * ( static_cast<float>( pBaseCombatWeapon->Clip1() ) / static_cast<float>( pBaseCombatWeapon->GetMaxClip1() ) );
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->Clip1Percentage() ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f ", fClip1Percentage );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f\n", fParam );
			}

			return ( bNot ) ? ( fClip1Percentage >= fParam ) : ( fClip1Percentage < fParam );
		}

		case LESSON_ACTION_WEAPON_AMMO_LOW:
		{
			int iTemp = static_cast<int>( fParam );

			C_BasePlayer *pBasePlayer = ToBasePlayer( pVar );

			if ( !pBasePlayer )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetWeaponInSlot( ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ")->AmmoPercentage() >= 30\n" ) : ( ")->AmmoPercentage() < 30\n" ) );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" );
				}

				return false;
			}

			CBaseCombatWeapon *pBaseCombatWeapon = NULL;

			// Get the weapon in variable slot
			for ( int iWeapon = 0; iWeapon < MAX_WEAPONS; iWeapon++ )
			{
				CBaseCombatWeapon *pBaseCombatWeaponTemp = pBasePlayer->GetWeapon( iWeapon );
				if ( pBaseCombatWeaponTemp )
				{
					if ( pBaseCombatWeaponTemp->GetSlot() == iTemp )
					{
						pBaseCombatWeapon = pBaseCombatWeaponTemp;
						break;
					}
				}
			}

			if ( !pBaseCombatWeapon )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetWeaponInSlot( ", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ")->AmmoPercentage() >= 30\n" ) : ( ")->AmmoPercentage() < 30\n" ) );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" );
				}

				return false;
			}

			// Check if the ammo is full
			int iAmmoType = pBaseCombatWeapon->GetPrimaryAmmoType();
			int iMaxAmmo = GetAmmoDef()->MaxCarry( iAmmoType/*, pBasePlayer*/ );
			int iPlayerAmmo = pBasePlayer->GetAmmoCount( iAmmoType );

			bool bAmmoLow = ( iPlayerAmmo < ( iMaxAmmo / 3 ) );

			if ( bNot )
			{
				bAmmoLow = !bAmmoLow;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetWeaponInSlot( ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ")->AmmoPercentage() >= 30 " ) : ( ")->AmmoPercentage() < 30 " ) );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bAmmoLow ) ? ( "true\n" ) : ( "false\n" ) );
			}

			return bAmmoLow;
		}

		case LESSON_ACTION_WEAPON_AMMO_FULL:
		{
			int iTemp = static_cast<int>( fParam );

			C_BasePlayer *pBasePlayer = ToBasePlayer( pVar );

			if ( !pBasePlayer )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoFull()\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" );
				}

				return false;
			}

			CBaseCombatWeapon *pBaseCombatWeapon = NULL;

			// Get the weapon in variable slot
			for ( int iWeapon = 0; iWeapon < MAX_WEAPONS; iWeapon++ )
			{
				CBaseCombatWeapon *pBaseCombatWeaponTemp = pBasePlayer->GetWeapon( iWeapon );
				if ( pBaseCombatWeaponTemp )
				{
					if ( pBaseCombatWeaponTemp->GetSlot() == iTemp )
					{
						pBaseCombatWeapon = pBaseCombatWeaponTemp;
						break;
					}
				}
			}

			if ( !pBaseCombatWeapon )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoFull()\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetWeaponInSlot returned NULL!\n" );
				}

				return false;
			}

			// Check if the ammo is full
			int iAmmoType = pBaseCombatWeapon->GetPrimaryAmmoType();
			int iMaxAmmo = GetAmmoDef()->MaxCarry( iAmmoType/*, pBasePlayer*/ );
			int iPlayerAmmo = pBasePlayer->GetAmmoCount( iAmmoType );

			bool bAmmoFull = ( iPlayerAmmo >= iMaxAmmo );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoFull() " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, ( bAmmoFull ) ? ( "true\n" ) : ( "false\n" ) );
			}

			return ( bNot ) ? ( !bAmmoFull ) : ( bAmmoFull );
		}

		case LESSON_ACTION_WEAPON_AMMO_EMPTY:
		{
			int iTemp = static_cast<int>( fParam );

			C_BasePlayer *pBasePlayer = ToBasePlayer( pVar );

			if ( !pBasePlayer )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoEmpty()\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" );
				}

				return false;
			}

			CBaseCombatWeapon *pBaseCombatWeapon = NULL;

			// Get the weapon in variable slot
			for ( int iWeapon = 0; iWeapon < MAX_WEAPONS; iWeapon++ )
			{
				CBaseCombatWeapon *pBaseCombatWeaponTemp = pBasePlayer->GetWeapon( iWeapon );
				if ( pBaseCombatWeaponTemp )
				{
					if ( pBaseCombatWeaponTemp->GetSlot() == iTemp )
					{
						pBaseCombatWeapon = pBaseCombatWeaponTemp;
						break;
					}
				}
			}

			if ( !pBaseCombatWeapon )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp );
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoEmpty()\n" );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetWeaponInSlot returned NULL!\n" );
				}

				return false;
			}

			// Check if the ammo is empty
			int iAmmoType = pBaseCombatWeapon->GetPrimaryAmmoType();
			int iPlayerAmmo = pBasePlayer->GetAmmoCount( iAmmoType );

			bool bAmmoEmpty = ( iPlayerAmmo <= 0 );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoEmpty() " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, ( bAmmoEmpty ) ? ( "true" ) : ( "false" ) );
				ConColorMsg(CBaseLesson::m_rgbaVerbosePlain, " )\n" );
			}

			return ( bNot ) ? ( !bAmmoEmpty ) : ( bAmmoEmpty );
		}

		/*case LESSON_ACTION_WEAPON_CAN_USE:
		{
			C_BaseCombatWeapon *pBaseCombatWeapon = dynamic_cast<C_BaseCombatWeapon*>( pParam );
			C_BasePlayer *pBasePlayer = ToBasePlayer( pVar );

			if ( !pBasePlayer )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_CanUse([%s])\n" ) : ( "\t[%s]->Weapon_CanUse([%s])\n" ), pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" );
				}

				return false;
			}

			if ( !pBaseCombatWeapon )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_CanUse([%s])\n" ) : ( "\t[%s]->Weapon_CanUse([%s])\n" ), pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam BaseCombatWeapon returned NULL!\n" );
				}

				return false;
			}

			bool bCanEquip = pBasePlayer->Weapon_CanUse( pBaseCombatWeapon );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_CanUse([%s]) " ) : ( "\t[%s]->Weapon_CanUse([%s]) " ), pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, ( bCanEquip ) ? ( "true\n" ) : ( "false\n" ) );
			}

			return ( bNot ) ? ( !bCanEquip ) : ( bCanEquip );
		}*/

		case LESSON_ACTION_USE_TARGET_IS:
		{
			C_BasePlayer *pBasePlayer = ToBasePlayer( pVar );

			if ( !pBasePlayer )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) != [%s]\n" ) : ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) == [%s]\n" ), pchVarName, pchParamName->String() );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as Player returned NULL!\n" );
				}

				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) != [%s]\n" ) : ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) == [%s]\n" ), pchVarName, pchParamName->String() );
			}

			return ( bNot ) ? ( C_BaseEntity::Instance( pBasePlayer->GetUseEntity() ) != pParam ) : ( C_BaseEntity::Instance( pBasePlayer->GetUseEntity() ) == pParam );
		}

		case LESSON_ACTION_GET_USE_TARGET:
		{
			int iTemp = static_cast<int>( fParam );

			if ( iTemp <= 0 || iTemp > 2 )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entityINVALID] = C_BaseEntity::Instance([%s]->GetUseEntity())\n", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" );
				}

				return false;
			}

			// Use entity2 if it was specified, otherwise, use entity1
			CHandle<C_BaseEntity> *pHandle;
			char const *pchParamNameTemp = NULL;

			if ( iTemp == 2 )
			{
				pHandle = &m_hEntity2;
				pchParamNameTemp = "entity2";
			}
			else
			{
				pHandle = &m_hEntity1;
				pchParamNameTemp = "entity1";
			}

			C_BasePlayer *pBasePlayer = ToBasePlayer( pVar );

			if ( !pBasePlayer )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetUseEntity())\n", pchParamNameTemp, pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as Player returned NULL!\n" );
				}

				return false;
			}

			pHandle->Set( C_BaseEntity::Instance( pBasePlayer->GetUseEntity() ) );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetUseEntity())\n", pchParamNameTemp, pchVarName );
			}

			return true;
		}

		/*case LESSON_ACTION_GET_POTENTIAL_USE_TARGET:
		{
			int iTemp = static_cast<int>( fParam );

			if ( iTemp <= 0 || iTemp > 2 )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entityINVALID] = C_BaseEntity::Instance([%s]->GetPotentialUseEntity())\n", pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" );
				}

				return false;
			}

			// Use entity2 if it was specified, otherwise, use entity1
			CHandle<C_BaseEntity> *pHandle;
			char const *pchParamNameTemp = NULL;

			if ( iTemp == 2 )
			{
				pHandle = &m_hEntity2;
				pchParamNameTemp = "entity2";
			}
			else
			{
				pHandle = &m_hEntity1;
				pchParamNameTemp = "entity1";
			}

			C_BasePlayer *pBasePlayer = ToBasePlayer( pVar );

			if ( !pBasePlayer )
			{
				if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
				{
					ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetPotentialUseEntity())\n", pchParamNameTemp, pchVarName );
					ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as Player returned NULL!\n" );
				}

				return false;
			}

			pHandle->Set( C_BaseEntity::Instance( pBasePlayer->GetPotentialUseEntity() ) );

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetPotentialUseEntity())\n", pchParamNameTemp, pchVarName );
			}

			return true;
		}*/
	}

	DevWarning( "Invalid lesson action type used with \"%s\" variable type.\n", pchVarName );

	return false;
}

bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, CGameInstructorSymbol *pchVar, const CGameInstructorSymbol *pchParamName, const char *pchParam )
{
	switch ( iAction )
	{
		case LESSON_ACTION_REFERENCE_OPEN:
		{
			const CBaseLesson *pLesson = GetGameInstructor().GetLesson( pchParamName->String() );
			if ( !pLesson )
			{
				DevWarning( "Invalid lesson specified: \"%s\".", pchParamName->String() );
				return false;
			}

			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( Color( 64, 128, 255, 255 ), ( bNot ) ? ( "\t!( [\"%s\"]->IsInstanceActive() " ) : ( "\t( [\"%s\"]->IsInstanceActive() " ), pchParamName->String() );
				ConColorMsg( Color( 255, 255, 255, 255 ), "\"%s\"", (pLesson->IsInstanceActive() ? "true" : "false") );
				ConColorMsg( Color( 64, 128, 255, 255 ), " )\n" );
			}

			return ( bNot ) ? ( !pLesson->IsInstanceActive() ) : ( pLesson->IsInstanceActive() );
		}

		case LESSON_ACTION_SET:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy([%s], [%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
			}

			*pchVar = pchParam;
			return true;

		case LESSON_ACTION_ADD:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcat([%s], [%s] ", pchVarName, pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" );
			}

			char szTemp[ 256 ];
			Q_strncpy( szTemp, pchVar->String(), sizeof( szTemp ) );
			Q_strncat( szTemp, pchParam, sizeof( szTemp ) );

			*pchVar = szTemp;
			return true;

		case LESSON_ACTION_IS:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcmp([%s] ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchVar->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") != 0\n" ) : ( ") == 0\n" ) );
			}

			return ( bNot ) ? ( Q_strcmp( pchVar->String(), pchParam ) != 0 ) : ( Q_strcmp( pchVar->String(), pchParam ) == 0 );

		case LESSON_ACTION_HAS_PREFIX:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tStringHasPrefix([%s] ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchVar->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") == false\n" ) : ( ") == true\n" ) );
			}

			return ( bNot ) ? ( !StringHasPrefix( pchVar->String(), pchParam ) ) : ( StringHasPrefix( pchVar->String(), pchParam ) );

		case LESSON_ACTION_LESS_THAN:
			if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcmp([%s] ", pchVarName );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\"%s\"", pchVar->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\"%s\"", pchParam );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") >= 0\n" ) : ( ") < 0\n" ) );
			}

			return ( bNot ) ? ( Q_strcmp( pchVar->String(), pchParam ) >= 0 ) : ( Q_strcmp( pchVar->String(), pchParam ) < 0 );
	}

	DevWarning( "Invalid lesson action type used with \"%s\" variable type.\n", pchVarName );

	return false;
}

LessonEvent_t * CScriptedIconLesson::AddOpenEvent()
{
	int iNewLessonEvent = m_OpenEvents.AddToTail();
	return &(m_OpenEvents[ iNewLessonEvent ]);
}

LessonEvent_t * CScriptedIconLesson::AddCloseEvent()
{
	int iNewLessonEvent = m_CloseEvents.AddToTail();
	return &(m_CloseEvents[ iNewLessonEvent ]);
}

LessonEvent_t * CScriptedIconLesson::AddSuccessEvent()
{
	int iNewLessonEvent = m_SuccessEvents.AddToTail();
	return &(m_SuccessEvents[ iNewLessonEvent ]);
}

LessonEvent_t * CScriptedIconLesson::AddOnOpenEvent()
{
	int iNewLessonEvent = m_OnOpenEvents.AddToTail();
	return &(m_OnOpenEvents[ iNewLessonEvent ]);
}

LessonEvent_t * CScriptedIconLesson::AddUpdateEvent()
{
	int iNewLessonEvent = m_UpdateEvents.AddToTail();
	return &(m_UpdateEvents[ iNewLessonEvent ]);
}

// Static method to init the keyvalues symbols used for comparisons
void CScriptedIconLesson::PreReadLessonsFromFile()
{
	static bool bFirstTime = true;
	if ( !bFirstTime )
		return;
	bFirstTime = false;

	// Run init info call macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_VARIABLE_INIT_SYMBOL
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_VARIABLE_INIT_SYMBOL
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_VARIABLE_INIT_SYMBOL
#define LESSON_VARIABLE_MACRO_STRING	LESSON_VARIABLE_INIT_SYMBOL
	LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING

	// And build the map of variable name to enum
	// Run string to int macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition)
#define LESSON_VARIABLE_MACRO			LESSON_SCRIPT_STRING_ADD_TO_MAP
#define LESSON_VARIABLE_MACRO_BOOL		LESSON_SCRIPT_STRING_ADD_TO_MAP
#define LESSON_VARIABLE_MACRO_EHANDLE	LESSON_SCRIPT_STRING_ADD_TO_MAP
#define LESSON_VARIABLE_MACRO_STRING	LESSON_SCRIPT_STRING_ADD_TO_MAP
	LESSON_VARIABLE_FACTORY
#undef LESSON_VARIABLE_MACRO
#undef LESSON_VARIABLE_MACRO_BOOL
#undef LESSON_VARIABLE_MACRO_EHANDLE
#undef LESSON_VARIABLE_MACRO_STRING

	// Set up mapping of field types
	g_TypeToParamTypeMap.Insert( "float", FIELD_FLOAT );
	g_TypeToParamTypeMap.Insert( "string", FIELD_STRING );
	g_TypeToParamTypeMap.Insert( "int", FIELD_INTEGER );
	g_TypeToParamTypeMap.Insert( "integer", FIELD_INTEGER );
	g_TypeToParamTypeMap.Insert( "short", FIELD_INTEGER );
	g_TypeToParamTypeMap.Insert( "long", FIELD_INTEGER );
	g_TypeToParamTypeMap.Insert( "bool", FIELD_BOOLEAN );
	g_TypeToParamTypeMap.Insert( "player", FIELD_CUSTOM );
	g_TypeToParamTypeMap.Insert( "entity", FIELD_EHANDLE );
	g_TypeToParamTypeMap.Insert( "convar", FIELD_EMBEDDED );
	g_TypeToParamTypeMap.Insert( "void", FIELD_VOID );

	// Set up the lesson action map
	
	CScriptedIconLesson::LessonActionMap.Insert( "scope in", LESSON_ACTION_SCOPE_IN );
	CScriptedIconLesson::LessonActionMap.Insert( "scope out", LESSON_ACTION_SCOPE_OUT );
	CScriptedIconLesson::LessonActionMap.Insert( "close", LESSON_ACTION_CLOSE );
	CScriptedIconLesson::LessonActionMap.Insert( "success", LESSON_ACTION_SUCCESS );
	CScriptedIconLesson::LessonActionMap.Insert( "lock", LESSON_ACTION_LOCK );
	CScriptedIconLesson::LessonActionMap.Insert( "present complete", LESSON_ACTION_PRESENT_COMPLETE );
	CScriptedIconLesson::LessonActionMap.Insert( "present start", LESSON_ACTION_PRESENT_START );
	CScriptedIconLesson::LessonActionMap.Insert( "present end", LESSON_ACTION_PRESENT_END );

	CScriptedIconLesson::LessonActionMap.Insert( "reference open", LESSON_ACTION_REFERENCE_OPEN );

	CScriptedIconLesson::LessonActionMap.Insert( "set", LESSON_ACTION_SET );
	CScriptedIconLesson::LessonActionMap.Insert( "add", LESSON_ACTION_ADD );
	CScriptedIconLesson::LessonActionMap.Insert( "subtract", LESSON_ACTION_SUBTRACT );
	CScriptedIconLesson::LessonActionMap.Insert( "multiply", LESSON_ACTION_MULTIPLY );
	CScriptedIconLesson::LessonActionMap.Insert( "is", LESSON_ACTION_IS );
	CScriptedIconLesson::LessonActionMap.Insert( "less than", LESSON_ACTION_LESS_THAN );
	CScriptedIconLesson::LessonActionMap.Insert( "has prefix", LESSON_ACTION_HAS_PREFIX );
	CScriptedIconLesson::LessonActionMap.Insert( "has bit", LESSON_ACTION_HAS_BIT );
	CScriptedIconLesson::LessonActionMap.Insert( "bit count is", LESSON_ACTION_BIT_COUNT_IS );
	CScriptedIconLesson::LessonActionMap.Insert( "bit count less than", LESSON_ACTION_BIT_COUNT_LESS_THAN );

	CScriptedIconLesson::LessonActionMap.Insert( "get distance", LESSON_ACTION_GET_DISTANCE );
	CScriptedIconLesson::LessonActionMap.Insert( "get angular distance", LESSON_ACTION_GET_ANGULAR_DISTANCE );
	CScriptedIconLesson::LessonActionMap.Insert( "get player display name", LESSON_ACTION_GET_PLAYER_DISPLAY_NAME );
	CScriptedIconLesson::LessonActionMap.Insert( "classname is", LESSON_ACTION_CLASSNAME_IS );
	CScriptedIconLesson::LessonActionMap.Insert( "modelname is", LESSON_ACTION_MODELNAME_IS );
	CScriptedIconLesson::LessonActionMap.Insert( "team is", LESSON_ACTION_TEAM_IS );
	CScriptedIconLesson::LessonActionMap.Insert( "health less than", LESSON_ACTION_HEALTH_LESS_THAN );
	CScriptedIconLesson::LessonActionMap.Insert( "health percentage less than", LESSON_ACTION_HEALTH_PERCENTAGE_LESS_THAN );
	CScriptedIconLesson::LessonActionMap.Insert( "get active weapon", LESSON_ACTION_GET_ACTIVE_WEAPON );
	CScriptedIconLesson::LessonActionMap.Insert( "weapon is", LESSON_ACTION_WEAPON_IS );
	CScriptedIconLesson::LessonActionMap.Insert( "weapon has", LESSON_ACTION_WEAPON_HAS );
	CScriptedIconLesson::LessonActionMap.Insert( "get active weapon slot", LESSON_ACTION_GET_ACTIVE_WEAPON_SLOT );
	CScriptedIconLesson::LessonActionMap.Insert( "get weapon slot", LESSON_ACTION_GET_WEAPON_SLOT );
	CScriptedIconLesson::LessonActionMap.Insert( "get weapon in slot", LESSON_ACTION_GET_WEAPON_IN_SLOT );
	CScriptedIconLesson::LessonActionMap.Insert( "clip percentage less than", LESSON_ACTION_CLIP_PERCENTAGE_LESS_THAN);
	CScriptedIconLesson::LessonActionMap.Insert( "weapon ammo low", LESSON_ACTION_WEAPON_AMMO_LOW );
	CScriptedIconLesson::LessonActionMap.Insert( "weapon ammo full", LESSON_ACTION_WEAPON_AMMO_FULL );
	CScriptedIconLesson::LessonActionMap.Insert( "weapon ammo empty", LESSON_ACTION_WEAPON_AMMO_EMPTY );
	CScriptedIconLesson::LessonActionMap.Insert( "weapon can use", LESSON_ACTION_WEAPON_CAN_USE );
	CScriptedIconLesson::LessonActionMap.Insert( "use target is", LESSON_ACTION_USE_TARGET_IS );
	CScriptedIconLesson::LessonActionMap.Insert( "get use target", LESSON_ACTION_GET_USE_TARGET );
	CScriptedIconLesson::LessonActionMap.Insert( "get potential use target", LESSON_ACTION_GET_POTENTIAL_USE_TARGET );

	// Add mod actions to the map
	//Mod_PreReadLessonsFromFile();
}

C_GameInstructor

And now, this class is the Game Instructor in charge of reading the files that contain the lessons to be learned, saving them, updating the information, showing them, etc... As before, we must add these 2 files in the Client:

c_gameinstructor.h

//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose:		Client handler for instruction players how to play
//
//=============================================================================//

#ifndef _C_GAMEINSTRUCTOR_H_
#define _C_GAMEINSTRUCTOR_H_


#include "GameEventListener.h"
#include "vgui_controls/phandle.h"

class CBaseLesson;


struct LessonGroupConVarToggle_t
{
	ConVarRef var;
	char szLessonGroupName[ 64 ];

	LessonGroupConVarToggle_t( const char *pchConVarName ) :
		var( pchConVarName )
	{
	}
};


class C_GameInstructor : public CAutoGameSystemPerFrame, public CGameEventListener
{
public:
	C_GameInstructor() : CAutoGameSystemPerFrame( "C_GameInstructor" )
	{
		m_bHasLoadedSaveData = false;
		m_bDirtySaveData = false;
	}


	// Methods of IGameSystem
	virtual bool Init( void );
	virtual void Shutdown( void );
	virtual void Update( float frametime );

	void UpdateHiddenByOtherElements( void );
	bool Mod_HiddenByOtherElements( void );

	virtual void FireGameEvent( IGameEvent *event );

	void DefineLesson( CBaseLesson *pLesson );

	const CBaseLesson * GetLesson( const char *pchLessonName );
	bool IsLessonOfSameTypeOpen( const CBaseLesson *pLesson ) const;

	bool ReadSaveData( void );
	bool WriteSaveData( void );
	void RefreshDisplaysAndSuccesses( void );
	void ResetDisplaysAndSuccesses( void );
	void MarkDisplayed( const char *pchLessonName );
	void MarkSucceeded( const char *pchLessonName );

	void PlaySound( const char *pchSoundName );

	bool OpenOpportunity( CBaseLesson *pLesson );

	void DumpOpenOpportunities( void );

	KeyValues * GetScriptKeys( void );
	C_BasePlayer * GetLocalPlayer( void );

	void EvaluateLessonsForGameRules( void );
	void SetLessonGroupEnabled( const char *pszGroup, bool bEnabled );

private:
	void FindErrors( void );

	bool UpdateActiveLesson( CBaseLesson *pLesson, const CBaseLesson *pRootLesson );
	void UpdateInactiveLesson( CBaseLesson *pLesson );

	CBaseLesson * GetLesson_Internal( const char *pchLessonName );

	void StopAllLessons( void );

	void CloseAllOpenOpportunities( void );
	void CloseOpportunity( CBaseLesson *pLesson );

	void ReadLessonsFromFile( const char *pchFileName );
	void InitLessonPrerequisites( void );

private:
	CUtlVector < CBaseLesson* >	m_Lessons;
	CUtlVector < CBaseLesson* >	m_OpenOpportunities;

	CUtlVector < LessonGroupConVarToggle_t > m_LessonGroupConVarToggles;

	KeyValues	*m_pScriptKeys;

	bool	m_bNoDraw;
	bool	m_bHiddenDueToOtherElements;

	int		m_iCurrentPriority;
	EHANDLE	m_hLastSpectatedPlayer;
	bool	m_bSpectatedPlayerChanged;

	char	m_szPreviousStartSound[ 128 ];
	float	m_fNextStartSoundTime;

	bool	m_bHasLoadedSaveData;
	bool	m_bDirtySaveData;
};

C_GameInstructor &GetGameInstructor();

void GameInstructor_Init();
void GameInstructor_Shutdown();


#endif // _C_GAMEINSTRUCTOR_H_

c_gameinstructor.cpp

//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Purpose:		Client handler implementations for instruction players how to play
//
//=============================================================================//

#include "cbase.h"

#include "c_gameinstructor.h"
#include "c_baselesson.h"
#include "filesystem.h"
#include "vprof.h"
#include "ixboxsystem.h"
#include "tier0/icommandline.h"
#include "iclientmode.h"

#ifdef INSOURCE
#include "in_player_shared.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//=========================================================
// Configuración
//=========================================================

#define MOD_DIR							"MOD"
#define GAMEINSTRUCTOR_SCRIPT_FILE		"scripts/instructor_lessons.txt"
#define GAMEINSTRUCTOR_MOD_SCRIPT_FILE	"scripts/mod_lessons.txt"

// Game instructor auto game system instantiation
C_GameInstructor g_GameInstructor;
C_GameInstructor &GetGameInstructor()
{
	return g_GameInstructor;
}

void GameInstructorEnable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue );
void SVGameInstructorDisable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue );

extern ConVar sv_gameinstructor_disable;

//=========================================================
// Comandos de consola
//=========================================================

ConVar gameinstructor_verbose("gameinstructor_verbose", "1", FCVAR_CHEAT, "Set to 1 for standard debugging or 2 (in combo with gameinstructor_verbose_lesson) to show update actions.");
ConVar gameinstructor_verbose_lesson("gameinstructor_verbose_lesson", "", FCVAR_CHEAT, "Display more verbose information for lessons have this name." );
ConVar gameinstructor_find_errors("gameinstructor_find_errors", "1", FCVAR_CHEAT, "Set to 1 and the game instructor will run EVERY scripted command to uncover errors." );

ConVar gameinstructor_enable( "gameinstructor_enable", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Display in game lessons that teach new players.", GameInstructorEnable_ChangeCallback );
ConVar gameinstructor_start_sound_cooldown( "gameinstructor_start_sound_cooldown", "4.0", FCVAR_NONE, "Number of seconds forced between similar lesson start sounds." );

ConVar sv_gameinstructor_disable( "sv_gameinstructor_disable", "0", FCVAR_REPLICATED, "Force all clients to disable their game instructors.", SVGameInstructorDisable_ChangeCallback );

//=========================================================
// Activa o Desactiva el Instructor del lado del cliente.
//=========================================================
void EnableDisableInstructor()
{
	bool bEnabled = ( !sv_gameinstructor_disable.GetBool() && gameinstructor_enable.GetBool() );

	// Game instructor has been enabled, so init it!
	if ( bEnabled )
		GetGameInstructor().Init();

	// Game instructor has been disabled, so shut it down!
	else 
		GetGameInstructor().Shutdown();
}

//=========================================================
//=========================================================
void GameInstructorEnable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue )
{
	if ( ( flOldValue != 0.0f ) != gameinstructor_enable.GetBool() )
		EnableDisableInstructor();
}

//=========================================================
//=========================================================
void SVGameInstructorDisable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue )
{
	if ( !engine )
		return;

	EnableDisableInstructor();
}

//=========================================================
// Inicializa al Instructor
//=========================================================
bool C_GameInstructor::Init()
{
//	if ( &GetGameInstructor() == this )
	//	return true;

	// Instructor desactivado, no inicializar.
	if ( !gameinstructor_enable.GetBool() || sv_gameinstructor_disable.GetBool() )
		return true;

	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Inicializando...\n" );
	}

	m_bNoDraw					= false;
	m_bHiddenDueToOtherElements = false;

	m_iCurrentPriority			= 0;
	m_hLastSpectatedPlayer		= NULL;
	m_bSpectatedPlayerChanged	= false;

	m_szPreviousStartSound[0]	= '\0';
	m_fNextStartSoundTime		= 0;

	ReadLessonsFromFile( GAMEINSTRUCTOR_MOD_SCRIPT_FILE );
	ReadLessonsFromFile( GAMEINSTRUCTOR_SCRIPT_FILE );

	InitLessonPrerequisites();
	ReadSaveData();

	ListenForGameEvent("gameinstructor_draw");
	ListenForGameEvent("gameinstructor_nodraw");

	ListenForGameEvent("round_end");
	ListenForGameEvent("round_start");
	ListenForGameEvent("player_death");
	ListenForGameEvent("player_team");
	ListenForGameEvent("player_disconnect");
	ListenForGameEvent("map_transition");
	ListenForGameEvent("game_newmap");
	ListenForGameEvent("set_instructor_group_enabled");

	EvaluateLessonsForGameRules();
	return true;
}

//=========================================================
// Apaga al instructor
//=========================================================
void C_GameInstructor::Shutdown()
{
	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Apagando...\n" );
	}

	CloseAllOpenOpportunities();
	WriteSaveData();

	// Removemos todas las lecciones.
	for ( int i = 0; i < m_Lessons.Count(); ++i )
	{
		if ( m_Lessons[ i ] )
		{
			m_Lessons[ i ]->StopListeningForAllEvents();
			delete m_Lessons[ i ];
			m_Lessons[ i ] = NULL;
		}
	}

	m_Lessons.RemoveAll();
	m_LessonGroupConVarToggles.RemoveAll();

	// Paramos de escuchar eventos.
	StopListeningForAllEvents();
}

//=========================================================
//=========================================================
void C_GameInstructor::UpdateHiddenByOtherElements()
{
	//bool bHidden = Mod_HiddenByOtherElements();
	bool bHidden = false;

	if ( bHidden && !m_bHiddenDueToOtherElements )
		StopAllLessons();

	m_bHiddenDueToOtherElements = bHidden;
}

//=========================================================
//=========================================================
void C_GameInstructor::Update( float frametime )
{
	VPROF_BUDGET( "C_GameInstructor::Update", "GameInstructor" );

	UpdateHiddenByOtherElements();

	// Instructor desactivado.
	if ( !gameinstructor_enable.GetBool() || m_bNoDraw || m_bHiddenDueToOtherElements )
		return;

	if ( gameinstructor_find_errors.GetBool() )
	{
		FindErrors();
		gameinstructor_find_errors.SetValue(0);
	}

	if ( IsConsole() )
	{
		// On X360 we want to save when they're not connected
		// They aren't in game
		if ( !engine->IsInGame() )
			WriteSaveData();
		else
		{
			const char *levelName = engine->GetLevelName();

			// The are in game, but it's a background map
			if ( levelName && levelName[0] && engine->IsLevelMainMenuBackground() )
				WriteSaveData();
		}
	}

	if ( m_bSpectatedPlayerChanged )
	{
		// Safe spot to clean out stale lessons if spectator changed
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Spectated player changed...\n" );
		}

		CloseAllOpenOpportunities();
		m_bSpectatedPlayerChanged = false;
	}

	// Loop through all the lesson roots and reset their active status
	for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i )
	{
		CBaseLesson *pLesson		= m_OpenOpportunities[ i ];
		CBaseLesson *pRootLesson	= pLesson->GetRoot();

		if ( pRootLesson->InstanceType() == LESSON_INSTANCE_SINGLE_ACTIVE )
			pRootLesson->SetInstanceActive(false);
	}

	int iCurrentPriority = 0;

	// Loop through all the open lessons
	for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i )
	{
		CBaseLesson *pLesson = m_OpenOpportunities[ i ];

		// This opportunity has closed
		if ( !pLesson->IsOpenOpportunity() || pLesson->IsTimedOut() )
		{
			CloseOpportunity( pLesson );
			continue;
		}

		// Lesson should be displayed, so it can affect priority
		CBaseLesson *pRootLesson	= pLesson->GetRoot();
		bool bShouldDisplay			= pLesson->ShouldDisplay();
		bool bIsLocked				= pLesson->IsLocked();

		if ( ( bShouldDisplay || bIsLocked ) && 
			 ( pLesson->GetPriority() >= m_iCurrentPriority || pLesson->NoPriority() || bIsLocked ) && 
			 ( pRootLesson && ( pRootLesson->InstanceType() != LESSON_INSTANCE_SINGLE_ACTIVE || !pRootLesson->IsInstanceActive() ) ) )
		{
			// Lesson is at the highest priority level, isn't violating instance rules, and has met all the prerequisites
			if ( UpdateActiveLesson( pLesson, pRootLesson ) || pRootLesson->IsLearned() )
			{
				// Lesson is active
				if ( pLesson->IsVisible() || pRootLesson->IsLearned() )
				{
					pRootLesson->SetInstanceActive( true );

					// This active or learned lesson has the highest priority so far
					if ( iCurrentPriority < pLesson->GetPriority() && !pLesson->NoPriority() )
						iCurrentPriority = pLesson->GetPriority();
				}
			}
			else
			{
				// On second thought, this shouldn't have been displayed
				bShouldDisplay = false;
			}
		}
		else
		{
			// Lesson shouldn't be displayed right now
			UpdateInactiveLesson( pLesson );
		}
	}

	// Set the priority for next frame
	if ( gameinstructor_verbose.GetInt() > 1 && m_iCurrentPriority != iCurrentPriority )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Priority changed from " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "%i ", m_iCurrentPriority );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "to " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "%i", iCurrentPriority );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" );
	}

	m_iCurrentPriority = iCurrentPriority;
}

//=========================================================
//=========================================================
void C_GameInstructor::FireGameEvent( IGameEvent *event )
{
	VPROF_BUDGET( "C_GameInstructor::FireGameEvent", "GameInstructor" );
	const char *name = event->GetName();

	if ( Q_strcmp( name, "gameinstructor_draw" ) == 0 )
	{
		if ( m_bNoDraw )
		{
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Set to draw...\n" );
			}

			m_bNoDraw = false;
		}
	}
	else if ( Q_strcmp( name, "gameinstructor_nodraw" ) == 0 )
	{
		if ( !m_bNoDraw )
		{
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Set to not draw...\n" );
			}

			m_bNoDraw = true;
			StopAllLessons();
		}
	}
	else if ( Q_strcmp( name, "round_end" ) == 0 )
	{
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Round ended...\n" );
		}

		CloseAllOpenOpportunities();

		if ( IsPC() )
		{
			// Good place to backup our counts
			WriteSaveData();
		}
	}
	else if ( Q_strcmp( name, "round_start" ) == 0 )
	{
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Round started...\n" );
		}

		CloseAllOpenOpportunities();

		EvaluateLessonsForGameRules();
	}
	else if ( Q_strcmp( name, "player_death" ) == 0 )
	{
		#if !defined(NO_STEAM) && defined(USE_CEG)
				Steamworks_TestSecret(); 
				Steamworks_SelfCheck(); 
		#endif

		C_BasePlayer *pLocalPlayer = GetLocalPlayer();

		if ( pLocalPlayer && pLocalPlayer == UTIL_PlayerByUserId( event->GetInt( "userid" ) ) )
		{
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Local player died...\n" );
			}

			for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i )
			{
				CBaseLesson *pLesson		= m_OpenOpportunities[ i ];
				CBaseLesson *pRootLesson	= pLesson->GetRoot();

				if ( !pRootLesson->CanOpenWhenDead() )
					CloseOpportunity( pLesson );
			}
		}
	}
	else if ( Q_strcmp( name, "player_team" ) == 0 )
	{
		C_BasePlayer *pLocalPlayer = GetLocalPlayer();

		if ( pLocalPlayer && pLocalPlayer == UTIL_PlayerByUserId( event->GetInt( "userid" ) ) && 
			 ( event->GetInt( "team" ) != event->GetInt( "oldteam" ) || event->GetBool( "disconnect" ) ) )
		{
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Local player changed team (or disconnected)...\n" );
			}

			CloseAllOpenOpportunities();
		}

		EvaluateLessonsForGameRules();
	}
	else if ( Q_strcmp( name, "player_disconnect" ) == 0 )
	{
		C_BasePlayer *pLocalPlayer = GetLocalPlayer();
		if ( pLocalPlayer && pLocalPlayer == UTIL_PlayerByUserId( event->GetInt( "userid" ) ) )
		{
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Local player disconnected...\n" );
			}

			CloseAllOpenOpportunities();
		}
	}
	else if ( Q_strcmp( name, "map_transition" ) == 0 )
	{
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Map transition...\n" );
		}

		CloseAllOpenOpportunities();

		if ( m_bNoDraw )
		{
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( Color( 255, 128, 64, 255 ), "[INSTRUCTOR]: " );
				ConColorMsg( Color( 64, 128, 255, 255 ), "Set to draw...\n" );
			}

			m_bNoDraw = false;
		}

		if ( IsPC() )
		{
			// Good place to backup our counts
			WriteSaveData();
		}
	}
	else if ( Q_strcmp( name, "game_newmap" ) == 0 )
	{
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "New map...\n" );
		}

		CloseAllOpenOpportunities();

		if ( m_bNoDraw )
		{
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( Color( 255, 128, 64, 255 ), "[INSTRUCTOR]: " );
				ConColorMsg( Color( 64, 128, 255, 255 ), "Set to draw...\n" );
			}

			m_bNoDraw = false;
		}

		if ( IsPC() )
		{
			// Good place to backup our counts
			WriteSaveData();
		}
	}

	else if ( Q_strcmp( name, "set_instructor_group_enabled" ) == 0 )
	{
		const char *pszGroup	= event->GetString( "group" );
		bool bEnabled			= event->GetInt( "enabled" ) != 0;

		if ( pszGroup && pszGroup[0] )
			SetLessonGroupEnabled(pszGroup, bEnabled);
	}
}

//=========================================================
//=========================================================
void C_GameInstructor::DefineLesson( CBaseLesson *pLesson )
{
	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Lesson " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pLesson->GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "defined.\n" );
	}

	m_Lessons.AddToTail( pLesson );
}

//=========================================================
//=========================================================
const CBaseLesson * C_GameInstructor::GetLesson( const char *pchLessonName )
{
	return GetLesson_Internal( pchLessonName );
}

//=========================================================
//=========================================================
bool C_GameInstructor::IsLessonOfSameTypeOpen( const CBaseLesson *pLesson ) const
{
	for ( int i = 0; i < m_OpenOpportunities.Count(); ++i )
	{
		CBaseLesson *pOpenOpportunity = m_OpenOpportunities[ i ];

		if ( pOpenOpportunity->GetNameSymbol() == pLesson->GetNameSymbol() )
			return true;
	}

	return false;
}

//=========================================================
//=========================================================
bool C_GameInstructor::ReadSaveData()
{
	// for external playtests, don't ever read in persisted instructor state, always start fresh
	if ( CommandLine()->FindParm( "-playtest" ) )
		return true;

	if ( m_bHasLoadedSaveData )
		return true;
	
	// Always reset state first in case storage device
	// was declined or ends up in faulty state
	ResetDisplaysAndSuccesses();

	m_bHasLoadedSaveData = true;

#ifdef _X360
	DevMsg( "Read Game Instructor for splitscreen slot %d\n", m_nSplitScreenSlot );

	if ( m_nSplitScreenSlot < 0 )
		return false;

	if ( m_nSplitScreenSlot >= (int) XBX_GetNumGameUsers() )
		return false;

	int iController = XBX_GetUserId( m_nSplitScreenSlot );

	if ( iController < 0 || XBX_GetUserIsGuest( iController ) )
	{
		// Can't read data for guests
		return false;
	}

	DWORD nStorageDevice = XBX_GetStorageDeviceId( iController );
	if ( !XBX_DescribeStorageDevice( nStorageDevice ) )
		return false;
#endif

	char szFilename[_MAX_PATH];

#ifdef _X360
	if ( IsX360() )
	{
		XBX_MakeStorageContainerRoot( iController, XBX_USER_SETTINGS_CONTAINER_DRIVE, szFilename, sizeof( szFilename ) );
		int nLen = strlen( szFilename );
		Q_snprintf( szFilename + nLen, sizeof( szFilename ) - nLen, ":\\game_instructor_counts.txt" );
	}
	else
#endif
	{
		Q_snprintf( szFilename, sizeof( szFilename ), "save/game_instructor_counts.txt" );
	}

	KeyValues *data = new KeyValues( "Game Instructor Counts" );
	KeyValues::AutoDelete autoDelete(data);

	if ( data->LoadFromFile( g_pFullFileSystem, szFilename, NULL ) )
	{
		int nVersion = 0;

		for ( KeyValues *pKey = data->GetFirstSubKey(); pKey; pKey = pKey->GetNextTrueSubKey() )
		{
			CBaseLesson *pLesson = GetLesson_Internal( pKey->GetName() );

			if ( pLesson )
			{
				pLesson->SetDisplayCount( pKey->GetInt( "display", 0 ) );
				pLesson->SetSuccessCount( pKey->GetInt( "success", 0 ) );

				if ( Q_strcmp( pKey->GetName(), "version number" ) == 0 )
				{
					nVersion = pLesson->GetSuccessCount();
				}
			}
		}

		CBaseLesson *pLessonVersionNumber = GetLesson_Internal( "version number" );
		if ( pLessonVersionNumber && !pLessonVersionNumber->IsLearned() )
		{
			ResetDisplaysAndSuccesses();
			pLessonVersionNumber->SetSuccessCount( pLessonVersionNumber->GetSuccessLimit() );
			m_bDirtySaveData = true;
		}


		return true;
	}

	// Couldn't read from the file
	return false;
}

//=========================================================
//=========================================================
bool C_GameInstructor::WriteSaveData()
{
	if ( engine->IsPlayingDemo() )
		return false;

	if ( !m_bDirtySaveData )
		return true;

#ifdef _X360
	float flPlatTime = Plat_FloatTime();

	static ConVarRef host_write_last_time( "host_write_last_time" );
	if ( host_write_last_time.IsValid() )
	{
		float flTimeSinceLastWrite = flPlatTime - host_write_last_time.GetFloat();
		if ( flTimeSinceLastWrite < 3.5f )
		{
			// Prevent writing to the same storage device twice in less than 3 second succession for TCR success!
			// This happens after leaving a game in splitscreen.
			//DevMsg( "Waiting to write Game Instructor for splitscreen slot %d... (%.1f seconds remain)\n", m_nSplitScreenSlot, 3.5f - flTimeSinceLastWrite );
			return false;
		}
	}
#endif

	// Always mark as clean state to avoid re-entry on
	// subsequent frames when storage device might be
	// in a yet-unmounted state.
	m_bDirtySaveData = false;

#ifdef _X360
	DevMsg( "Write Game Instructor for splitscreen slot %d at time: %.1f\n", m_nSplitScreenSlot, flPlatTime );

	if ( m_nSplitScreenSlot < 0 )
		return false;

	if ( m_nSplitScreenSlot >= (int) XBX_GetNumGameUsers() )
		return false;

	int iController = XBX_GetUserId( m_nSplitScreenSlot );

	if ( iController < 0 || XBX_GetUserIsGuest( iController ) )
	{
		// Can't save data for guests
		return false;
	}

	DWORD nStorageDevice = XBX_GetStorageDeviceId( iController );
	if ( !XBX_DescribeStorageDevice( nStorageDevice ) )
		return false;
#endif

	// Build key value data to save
	KeyValues *data = new KeyValues( "Game Instructor Counts" );
	KeyValues::AutoDelete autoDelete(data);

	for ( int i = 0; i < m_Lessons.Count(); ++i )
	{
		CBaseLesson *pLesson = m_Lessons[i];
		
		int iDisplayCount = pLesson->GetDisplayCount();
		int iSuccessCount = pLesson->GetSuccessCount();

		if ( iDisplayCount || iSuccessCount )
		{
			// We've got some data worth saving
			KeyValues *pKVData = new KeyValues( pLesson->GetName() );

			if ( iDisplayCount )
				pKVData->SetInt( "display", iDisplayCount );

			if ( iSuccessCount )
				pKVData->SetInt( "success", iSuccessCount );

			data->AddSubKey( pKVData );
		}
	}

	// Save it!
	CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );

	data->RecursiveSaveToFile( buf, 0 );

	char	szFilename[_MAX_PATH];

#ifdef _X360
	if ( IsX360() )
	{
		XBX_MakeStorageContainerRoot( iController, XBX_USER_SETTINGS_CONTAINER_DRIVE, szFilename, sizeof( szFilename ) );
		int nLen = strlen( szFilename );
		Q_snprintf( szFilename + nLen, sizeof( szFilename ) - nLen, ":\\game_instructor_counts.txt" );
	}
	else
#endif
	{
		Q_snprintf( szFilename, sizeof( szFilename ), "save/game_instructor_counts.txt" );
		filesystem->CreateDirHierarchy( "save", "MOD" );
	}

	bool bWriteSuccess = filesystem->WriteFile( szFilename, MOD_DIR, buf );

#ifdef _X360
	if ( xboxsystem )
	{
		xboxsystem->FinishContainerWrites( iController );
	}
#endif

	return bWriteSuccess;
}

//=========================================================
//=========================================================
void C_GameInstructor::RefreshDisplaysAndSuccesses()
{
	m_bHasLoadedSaveData = false;
	ReadSaveData();
}

//=========================================================
//=========================================================
void C_GameInstructor::ResetDisplaysAndSuccesses()
{
	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Reset all lesson display and success counts.\n" );
	}

	for ( int i = 0; i < m_Lessons.Count(); ++i )
	{
		m_Lessons[ i ]->ResetDisplaysAndSuccesses();
	}

	m_bDirtySaveData = false;
}

//=========================================================
//=========================================================
void C_GameInstructor::MarkDisplayed( const char *pchLessonName )
{
	CBaseLesson *pLesson = GetLesson_Internal(pchLessonName);

	if ( !pLesson )
		return;

	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Lesson " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLesson->GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "marked as displayed.\n" );
	}

	if ( pLesson->IncDisplayCount() )
		m_bDirtySaveData = true;
}

//=========================================================
//=========================================================
void C_GameInstructor::MarkSucceeded(const char *pchLessonName)
{
	CBaseLesson *pLesson = GetLesson_Internal(pchLessonName);

	if ( !pLesson )
		return;

	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Lesson " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\" ", pLesson->GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "marked as succeeded.\n" );
	}

	if ( pLesson->IncSuccessCount() )
		m_bDirtySaveData = true;
}

//=========================================================
//=========================================================
void C_GameInstructor::PlaySound( const char *pchSoundName )
{
	// emit alert sound
	C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();

	if ( pLocalPlayer )
	{
		// Local player exists
		if ( pchSoundName[ 0 ] != '\0' && Q_strcmp( m_szPreviousStartSound, pchSoundName ) != 0 )
		{
			Q_strcpy( m_szPreviousStartSound, pchSoundName );
			m_fNextStartSoundTime = 0.0f;
		}

		if ( gpGlobals->curtime >= m_fNextStartSoundTime && pchSoundName[ 0 ] != '\0' )
		{
			// A sound was specified, so play it!
			pLocalPlayer->EmitSound( pchSoundName );
			m_fNextStartSoundTime = gpGlobals->curtime + gameinstructor_start_sound_cooldown.GetFloat();
		}
	}
}

//=========================================================
//=========================================================
bool C_GameInstructor::OpenOpportunity( CBaseLesson *pLesson )
{
	// Get the root lesson
	CBaseLesson *pRootLesson = pLesson->GetRoot();

	if ( !pRootLesson )
	{
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
			ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (because root lesson could not be found).\n" );
		}

		delete pLesson;
		return false;
	}

	C_BasePlayer *pLocalPlayer = GetLocalPlayer();

	if ( !pRootLesson->CanOpenWhenDead() && ( !pLocalPlayer || !pLocalPlayer->IsAlive() ) )
	{
		// If the player is dead don't allow lessons that can't be opened when dead
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
			ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (because player is dead and can_open_when_dead not set).\n" );
		}

		delete pLesson;
		return false;
	}

	if ( !pRootLesson->PrerequisitesHaveBeenMet() )
	{
		// If the prereqs haven't been met, don't open it
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
			ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (because prereqs haven't been met).\n" );
		}

		delete pLesson;
		return false;
	}

	if ( pRootLesson->InstanceType() == LESSON_INSTANCE_FIXED_REPLACE )
	{
		CBaseLesson *pLessonToReplace = NULL;
		CBaseLesson *pLastReplacableLesson = NULL;

		int iInstanceCount = 0;

		// Check how many are already open
		for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i )
		{
			CBaseLesson *pOpenOpportunity = m_OpenOpportunities[ i ];

			if ( pOpenOpportunity->GetNameSymbol() == pLesson->GetNameSymbol() && 
				 pOpenOpportunity->GetReplaceKeySymbol() == pLesson->GetReplaceKeySymbol() )
			{
				iInstanceCount++;

				if ( pRootLesson->ShouldReplaceOnlyWhenStopped() )
				{
					if ( !pOpenOpportunity->IsInstructing() )
					{
						pLastReplacableLesson = pOpenOpportunity;
					}
				}
				else
				{
					pLastReplacableLesson = pOpenOpportunity;
				}

				if ( iInstanceCount >= pRootLesson->GetFixedInstancesMax() )
				{
					pLessonToReplace = pLastReplacableLesson;
					break;
				}
			}
		}

		if ( pLessonToReplace )
		{
			// Take the place of the previous instance
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLesson->GetName() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "replacing open lesson of same type.\n" );
			}

			pLesson->TakePlaceOf( pLessonToReplace );
			CloseOpportunity( pLessonToReplace );
		}
		else if ( iInstanceCount >= pRootLesson->GetFixedInstancesMax() )
		{
			// Don't add another lesson of this type
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (there is too many started lessons of this type).\n" );
			}

			delete pLesson;
			return false;
		}
	}

	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLesson->GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "opened.\n" );
	}

	m_OpenOpportunities.AddToTail( pLesson );

	return true;
}

//=========================================================
//=========================================================
void C_GameInstructor::DumpOpenOpportunities()
{
	ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
	ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Open lessons...\n" );

	for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i )
	{
		CBaseLesson *pLesson = m_OpenOpportunities[ i ];
		CBaseLesson *pRootLesson = pLesson->GetRoot();

		Color color;

		if ( pLesson->IsInstructing() )
		{
			// Green
			color = CBaseLesson::m_rgbaVerboseOpen;
		}
		else if ( pRootLesson->IsLearned() && pLesson->GetPriority() >= m_iCurrentPriority )
		{
			// Yellow
			color = CBaseLesson::m_rgbaVerboseSuccess;
		}
		else
		{
			// Red
			color = CBaseLesson::m_rgbaVerboseClose;
		}

		ConColorMsg( color, "\t%s\n", pLesson->GetName() );
	}
}

//=========================================================
//=========================================================
KeyValues * C_GameInstructor::GetScriptKeys()
{
	return m_pScriptKeys;
}

//=========================================================
//=========================================================
C_BasePlayer * C_GameInstructor::GetLocalPlayer()
{
	C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();

	// If we're not a developer, don't do the special spectator hook ups
	if ( !developer.GetBool() )
		return pLocalPlayer;

	// If there is no local player and we're not spectating, just return that
	if ( !pLocalPlayer || pLocalPlayer->GetTeamNumber() != TEAM_SPECTATOR )
		return pLocalPlayer;

	// We're purely a spectator let's get lessons of the person we're spectating
	C_BasePlayer *pSpectatedPlayer = NULL;

	if ( pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE || pLocalPlayer->GetObserverMode() == OBS_MODE_CHASE )
		pSpectatedPlayer = ToBasePlayer( pLocalPlayer->GetObserverTarget() );

	if ( m_hLastSpectatedPlayer != pSpectatedPlayer )
	{
		// We're spectating someone new! Close all the stale lessons!
		m_bSpectatedPlayerChanged = true;
		m_hLastSpectatedPlayer = pSpectatedPlayer;
	}

	return pSpectatedPlayer;
}

//=========================================================
//=========================================================
void C_GameInstructor::EvaluateLessonsForGameRules()
{
	// Enable everything by default
	for ( int i = 0; i < m_Lessons.Count(); ++i )
		m_Lessons[ i ]->SetEnabled(true);

	// Then see if we should disable anything
	for ( int nConVar = 0; nConVar < m_LessonGroupConVarToggles.Count(); ++nConVar )
	{
		LessonGroupConVarToggle_t *pLessonGroupConVarToggle = &(m_LessonGroupConVarToggles[ nConVar ]);

		if ( pLessonGroupConVarToggle->var.IsValid() )
		{
			if ( pLessonGroupConVarToggle->var.GetBool() )
				SetLessonGroupEnabled( pLessonGroupConVarToggle->szLessonGroupName, false );
		}
	}
}

//=========================================================
//=========================================================
void C_GameInstructor::SetLessonGroupEnabled( const char *pszGroup, bool bEnabled )
{
	for ( int i = 0; i < m_Lessons.Count(); ++i )
	{
		if ( !Q_stricmp(pszGroup, m_Lessons[i]->GetGroup()) )
			m_Lessons[i]->SetEnabled( bEnabled );
	}
}

//=========================================================
//=========================================================
void C_GameInstructor::FindErrors()
{
	// Loop through all the lesson and run all their scripted actions
	for ( int i = 0; i < m_Lessons.Count(); ++i )
	{
		CScriptedIconLesson *pLesson = dynamic_cast<CScriptedIconLesson *>( m_Lessons[ i ] );
		if ( pLesson )
		{
			// Process all open events
			for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetOpenEvents().Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(pLesson->GetOpenEvents()[ iLessonEvent ]);
				pLesson->ProcessElements( NULL, &(pLessonEvent->elements) );
			}

			// Process all close events
			for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetCloseEvents().Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(pLesson->GetCloseEvents()[ iLessonEvent ]);
				pLesson->ProcessElements( NULL, &(pLessonEvent->elements) );
			}

			// Process all success events
			for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetSuccessEvents().Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(pLesson->GetSuccessEvents()[ iLessonEvent ]);
				pLesson->ProcessElements( NULL, &(pLessonEvent->elements) );
			}

			// Process all on open events
			for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetOnOpenEvents().Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(pLesson->GetOnOpenEvents()[ iLessonEvent ]);
				pLesson->ProcessElements( NULL, &(pLessonEvent->elements) );
			}

			// Process all update events
			for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetUpdateEvents().Count(); ++iLessonEvent )
			{
				const LessonEvent_t *pLessonEvent = &(pLesson->GetUpdateEvents()[ iLessonEvent ]);
				pLesson->ProcessElements( NULL, &(pLessonEvent->elements) );
			}
		}
	}
}

//=========================================================
//=========================================================
bool C_GameInstructor::UpdateActiveLesson( CBaseLesson *pLesson, const CBaseLesson *pRootLesson )
{
	VPROF_BUDGET( "C_GameInstructor::UpdateActiveLesson", "GameInstructor" );

	bool bIsOpen = pLesson->IsInstructing();

	if ( !bIsOpen && !pRootLesson->IsLearned() )
	{
		pLesson->SetStartTime();
		pLesson->Start();

		// Check to see if it successfully started
		bIsOpen = ( pLesson->IsOpenOpportunity() && pLesson->ShouldDisplay() );

		if ( bIsOpen )
		{
			// Lesson hasn't been started and hasn't been learned
			if ( gameinstructor_verbose.GetInt() > 0 )
			{
				ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Started lesson " );
				ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pLesson->GetName() );
				ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" );
			}
		}
		else
		{
			pLesson->Stop();
			pLesson->ResetStartTime();
		}
	}

	if ( bIsOpen )
	{
		// Update the running lesson
		pLesson->Update();
		return true;
	}
	else
	{
		pLesson->UpdateInactive();
		return false;
	}
}

//=========================================================
//=========================================================
void C_GameInstructor::UpdateInactiveLesson( CBaseLesson *pLesson )
{
	VPROF_BUDGET( "C_GameInstructor::UpdateInactiveLesson", "GameInstructor" );

	if ( pLesson->IsInstructing() )
	{
		// Lesson hasn't been stopped
		if ( gameinstructor_verbose.GetInt() > 0 )
		{
			ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Stopped lesson " );
			ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\"", pLesson->GetName() );
			ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" );
		}

		pLesson->Stop();
		pLesson->ResetStartTime();
	}

	pLesson->UpdateInactive();
}

//=========================================================
//=========================================================
CBaseLesson * C_GameInstructor::GetLesson_Internal( const char *pchLessonName )
{
	for ( int i = 0; i < m_Lessons.Count(); ++i )
	{
		CBaseLesson *pLesson = m_Lessons[ i ];

		if ( Q_strcmp( pLesson->GetName(), pchLessonName ) == 0 )
		{
			return pLesson;
		}
	}

	return NULL;
}

//=========================================================
//=========================================================
void C_GameInstructor::StopAllLessons()
{
	// Stop all the current lessons
	for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i )
	{
		CBaseLesson *pLesson = m_OpenOpportunities[ i ];
		UpdateInactiveLesson( pLesson );
	}
}

//=========================================================
//=========================================================
void C_GameInstructor::CloseAllOpenOpportunities()
{
	// Clear out all the open opportunities
	for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i )
	{
		CBaseLesson *pLesson = m_OpenOpportunities[ i ];
		CloseOpportunity( pLesson );
	}

	Assert( m_OpenOpportunities.Count() == 0 );
}

//=========================================================
//=========================================================
void C_GameInstructor::CloseOpportunity( CBaseLesson *pLesson )
{
	UpdateInactiveLesson( pLesson );

	if ( pLesson->WasDisplayed() )
		MarkDisplayed( pLesson->GetName() );

	if ( gameinstructor_verbose.GetInt() > 0 )
	{
		ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() );
		ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "closed for reason: " );
		ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "%s\n", pLesson->GetCloseReason() );
	}

	pLesson->StopListeningForAllEvents();

	m_OpenOpportunities.FindAndRemove( pLesson );
	delete pLesson;
}

//=========================================================
//=========================================================
void C_GameInstructor::ReadLessonsFromFile( const char *pchFileName )
{
	// Static init function
	CScriptedIconLesson::PreReadLessonsFromFile();
	MEM_ALLOC_CREDIT();
	
	KeyValues *pLessonKeys = new KeyValues("instructor_lessons");
	KeyValues::AutoDelete autoDelete(pLessonKeys);

	pLessonKeys->LoadFromFile(g_pFullFileSystem, pchFileName, NULL);

	for ( m_pScriptKeys = pLessonKeys->GetFirstTrueSubKey(); m_pScriptKeys; m_pScriptKeys = m_pScriptKeys->GetNextTrueSubKey() )
	{
		if ( Q_stricmp(m_pScriptKeys->GetName(), "GroupConVarToggle") == 0 )
		{
			// Add convar group toggler to the list
			int nLessonGroupConVarToggle							= m_LessonGroupConVarToggles.AddToTail( LessonGroupConVarToggle_t( m_pScriptKeys->GetString( "convar" ) ) );
			LessonGroupConVarToggle_t *pLessonGroupConVarToggle		= &(m_LessonGroupConVarToggles[nLessonGroupConVarToggle]);

			Q_strcpy( pLessonGroupConVarToggle->szLessonGroupName, m_pScriptKeys->GetString("group") );
			continue;
		}

		// Ensure that lessons aren't added twice
		if ( GetLesson_Internal(m_pScriptKeys->GetName()) )
		{
			DevWarning("Lesson \"%s\" defined twice!\n", m_pScriptKeys->GetName());
			continue;
		}

		CScriptedIconLesson *pNewLesson = new CScriptedIconLesson(m_pScriptKeys->GetName(), false, false);
		GetGameInstructor().DefineLesson(pNewLesson);
	}

	m_pScriptKeys = NULL;
}

//=========================================================
//=========================================================
void C_GameInstructor::InitLessonPrerequisites()
{
	for ( int i = 0; i < m_Lessons.Count(); ++i )
		m_Lessons[ i ]->InitPrerequisites();
}

//=========================================================
// Comandos
//=========================================================

CON_COMMAND_F( gameinstructor_reload_lessons, "Shuts down all open lessons and reloads them from the script file.", FCVAR_CHEAT )
{
	GetGameInstructor().Shutdown();
	GetGameInstructor().Init();
}

CON_COMMAND_F( gameinstructor_reset_counts, "Resets all display and success counts to zero.", FCVAR_NONE )
{
	GetGameInstructor().ResetDisplaysAndSuccesses();
}

CON_COMMAND_F( gameinstructor_dump_open_lessons, "Gives a list of all currently open lessons.", FCVAR_CHEAT )
{
	GetGameInstructor().DumpOpenOpportunities();
}

Fixing problems

The Game Instructor has already been incorporated however it is now necessary to fix certain problems with the other files.

UTIL_CountNumBitsSet

This function is necessary for the operation of the lessons and should be in the file game/shared/util_shared.cpp so we will open such file and look for the function UTIL_StringFieldToInt approximately on the line 1089:

int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings )
{
	if ( !szValue || !szValue[0] )
		return -1;

	for ( int i = 0; i < iNumStrings; i++ )
	{
		if ( FStrEq(szValue, pValueStrings[i]) )
			return i;
	}

	Assert(0);
	return -1;
}

And just below it we will incorporate this code:

static char s_NumBitsInNibble[ 16 ] = 
{
	0, // 0000 = 0
	1, // 0001 = 1
	1, // 0010 = 2
	2, // 0011 = 3
	1, // 0100 = 4
	2, // 0101 = 5
	2, // 0110 = 6
	3, // 0111 = 7
	1, // 1000 = 8
	2, // 1001 = 9
	2, // 1010 = 10
	3, // 1011 = 11
	2, // 1100 = 12
	3, // 1101 = 13
	3, // 1110 = 14
	4, // 1111 = 15
};

int UTIL_CountNumBitsSet( unsigned int nVar )
{
	int nNumBits = 0;

	while ( nVar > 0 )
	{
		// Look up and add in bits in the bottom nibble
		nNumBits += s_NumBitsInNibble[ nVar & 0x0f ];

		// Shift one nibble to the right
		nVar >>= 4;
	}

	return nNumBits;
}

int UTIL_CountNumBitsSet( uint64 nVar )
{
	int nNumBits = 0;

	while ( nVar > 0 )
	{
		// Look up and add in bits in the bottom nibble
		nNumBits += s_NumBitsInNibble[ nVar & 0x0f ];

		// Shift one nibble to the right
		nVar >>= 4;
	}

	return nNumBits;
}

And now, as is logical, we will define those 2 functions inside game/shared/util_shared.h, as before we will look for the definition of the function UTIL_StringFieldToInt which is approximately on the line 587:

int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings );

And just below it we add:

int UTIL_CountNumBitsSet( unsigned int nVar );
int UTIL_CountNumBitsSet( uint64 nVar );

GetPlayerName

This function, as it says, serves to obtain the name of a player, however in the Source SDK 2013 it is only accessible from CBasePlayer while the Game Instructor requires it in CBaseEntity. So we will open the file game/client/c_baseentity.h and we will add this function in some publicly accessible section "public:", in my case I have put it under GetDebugName that is approximately on line 860:

char const						*GetDebugName( void );

Now below it we add:

virtual const char				*GetPlayerName() const { return NULL; }

CHudIcons

This class is responsible for displaying the icons next to the message when a lesson appears on the screen and it does not exist in the Source SDK 2013. To add it we must open the file "game/client/hud.h" 'and approximately on line' "195" 'we will find the following:

extern CHud gHUD;

Now below it we add the class:

//-----------------------------------------------------------------------------
// Purpose: CHudIcons
//-----------------------------------------------------------------------------
class CHudIcons
{
public:
	CHudIcons();
	~CHudIcons();

	void						Init();
	void						Shutdown();

	CHudTexture					*GetIcon( const char *szIcon );

	// loads a new icon into the list, without duplicates
	CHudTexture					*AddUnsearchableHudIconToList( CHudTexture& texture );
	CHudTexture					*AddSearchableHudIconToList( CHudTexture& texture );

	void						RefreshHudTextures();

private:

	void						SetupNewHudTexture( CHudTexture *t );
	bool						m_bHudTexturesLoaded;
	// Global list of known icons
	CUtlDict< CHudTexture *, int >		m_Icons;

};

CHudIcons &HudIcons();

Now we open the file game/client/hud.cpp and the final of the whole file that is to say in the line 1200 add the following:

CHudIcons::CHudIcons() :
	m_bHudTexturesLoaded( false )
{
}

CHudIcons::~CHudIcons()
{
	int c = m_Icons.Count();
	for ( int i = c - 1; i >= 0; i-- )
	{
		CHudTexture *tex = m_Icons[ i ];
		g_HudTextureMemoryPool.Free( tex );
	}
	m_Icons.Purge();
}

void CHudIcons::Init()
{
	if ( m_bHudTexturesLoaded )
		return;

	m_bHudTexturesLoaded = true;
	CUtlDict< CHudTexture *, int >	textureList;

	// check to see if we have sprites for this res; if not, step down
	LoadHudTextures( textureList, "scripts/hud_textures", NULL );
	LoadHudTextures( textureList, "scripts/mod_textures", NULL );

	LoadHudTextures( textureList, "scripts/instructor_textures", NULL );
	LoadHudTextures( textureList, "scripts/instructor_modtextures", NULL );

	int c = textureList.Count();
	for ( int index = 0; index < c; index++ )
	{
		CHudTexture* tex = textureList[ index ];
		AddSearchableHudIconToList( *tex );
	}

	FreeHudTextureList( textureList );
}

void CHudIcons::Shutdown()
{
	m_bHudTexturesLoaded = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CHudTexture *CHudIcons::AddUnsearchableHudIconToList( CHudTexture& texture )
{
	// These names are composed based on the texture file name
	char composedName[ 512 ];

	if ( texture.bRenderUsingFont )
	{
		Q_snprintf( composedName, sizeof( composedName ), "%s_c%i",
			texture.szTextureFile, texture.cCharacterInFont );
	}
	else
	{
		Q_snprintf( composedName, sizeof( composedName ), "%s_%i_%i_%i_%i",
			texture.szTextureFile, texture.rc.left, texture.rc.top, texture.rc.right, texture.rc.bottom );
	}

	CHudTexture *icon = GetIcon( composedName );
	if ( icon )
	{
		return icon;
	}

	CHudTexture *newTexture = ( CHudTexture * )g_HudTextureMemoryPool.Alloc();
	*newTexture = texture;

	SetupNewHudTexture( newTexture );

	int idx = m_Icons.Insert( composedName, newTexture );
	return m_Icons[ idx ];
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CHudTexture *CHudIcons::AddSearchableHudIconToList( CHudTexture& texture )
{
	CHudTexture *icon = GetIcon( texture.szShortName );
	if ( icon )
	{
		return icon;
	}

	CHudTexture *newTexture = ( CHudTexture * )g_HudTextureMemoryPool.Alloc();
	*newTexture = texture;

	SetupNewHudTexture( newTexture );

	int idx = m_Icons.Insert( texture.szShortName, newTexture );
	return m_Icons[ idx ];
}

//-----------------------------------------------------------------------------
// Purpose: returns a pointer to an icon in the list
//-----------------------------------------------------------------------------
CHudTexture *CHudIcons::GetIcon( const char *szIcon )
{
	int i = m_Icons.Find( szIcon );
	if ( i == m_Icons.InvalidIndex() )
		return NULL;

	return m_Icons[ i ];
}

//-----------------------------------------------------------------------------
// Purpose: Gets texture handles for the hud icon
//-----------------------------------------------------------------------------
void CHudIcons::SetupNewHudTexture( CHudTexture *t )
{
	if ( t->bRenderUsingFont )
	{
		vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" );
		t->hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( t->szTextureFile, true );
		t->rc.top = 0;
		t->rc.left = 0;
		t->rc.right = vgui::surface()->GetCharacterWidth( t->hFont, t->cCharacterInFont );
		t->rc.bottom = vgui::surface()->GetFontTall( t->hFont );
	}
	else
	{
		// Set up texture id and texture coordinates
		t->textureId = vgui::surface()->CreateNewTextureID();
		vgui::surface()->DrawSetTextureFile( t->textureId, t->szTextureFile, false, false );

		int wide, tall;
		vgui::surface()->DrawGetTextureSize( t->textureId, wide, tall );

		t->texCoords[ 0 ] = (float)(t->rc.left + 0.5f) / (float)wide;
		t->texCoords[ 1 ] = (float)(t->rc.top + 0.5f) / (float)tall;
		t->texCoords[ 2 ] = (float)(t->rc.right - 0.5f) / (float)wide;
		t->texCoords[ 3 ] = (float)(t->rc.bottom - 0.5f) / (float)tall;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CHudIcons::RefreshHudTextures()
{
	if ( !m_bHudTexturesLoaded )
	{
		Assert( 0 );
		return;
	}

	CUtlDict< CHudTexture *, int >	textureList;

	// check to see if we have sprites for this res; if not, step down
	LoadHudTextures( textureList, "scripts/hud_textures", NULL );
	LoadHudTextures( textureList, "scripts/mod_textures", NULL );

	LoadHudTextures( textureList, "scripts/instructor_textures", NULL );


	// fix up all the texture icons first
	int c = textureList.Count();
	for ( int index = 0; index < c; index++ )
	{
		CHudTexture *tex = textureList[ index ];
		Assert( tex );

		CHudTexture *icon = GetIcon( tex->szShortName );
		if ( !icon )
			continue;

		// Update file
		Q_strncpy( icon->szTextureFile, tex->szTextureFile, sizeof( icon->szTextureFile ) );

		if ( !icon->bRenderUsingFont )
		{
			// Update subrect
			icon->rc = tex->rc;

			// Keep existing texture id, but now update texture file and texture coordinates
			vgui::surface()->DrawSetTextureFile( icon->textureId, icon->szTextureFile, false, false );

			// Get new texture dimensions in case it changed
			int wide, tall;
			vgui::surface()->DrawGetTextureSize( icon->textureId, wide, tall );

			// Assign coords
			icon->texCoords[ 0 ] = (float)(icon->rc.left + 0.5f) / (float)wide;
			icon->texCoords[ 1 ] = (float)(icon->rc.top + 0.5f) / (float)tall;
			icon->texCoords[ 2 ] = (float)(icon->rc.right - 0.5f) / (float)wide;
			icon->texCoords[ 3 ] = (float)(icon->rc.bottom - 0.5f) / (float)tall;
		}
	}

	FreeHudTextureList( textureList );

	// fixup all the font icons
	vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" );
	for (int i = m_Icons.First(); m_Icons.IsValidIndex(i); i = m_Icons.Next(i))
	{
		CHudTexture *icon = m_Icons[i];
		if ( !icon )
			continue;

		// Update file
		if ( icon->bRenderUsingFont )
		{
			icon->hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( icon->szTextureFile, true );
			icon->rc.top = 0;
			icon->rc.left = 0;
			icon->rc.right = vgui::surface()->GetCharacterWidth( icon->hFont, icon->cCharacterInFont );
			icon->rc.bottom = vgui::surface()->GetFontTall( icon->hFont );
		}
	}
}


static CHudIcons g_HudIcons;

CHudIcons &HudIcons()
{
	return g_HudIcons;
}

As you can see in the previous code the icons will be defined in the files: scripts/hud_textures.txt, scripts/mod_textures.txt and scripts/instructor_textures.txt

Although that is not all, we have already added the class but now it is necessary to start / prepare it, for that in that same file (hud.cpp) we will look for the function CHud::Init that is approximately on the line 395 and at the end of the function (Under "FreeHudTextureList (textureList);") we add the following:

HudIcons().Init();

GetColor

The code for the console commands "ConVar" has a new function called GetColor which, as its name indicates, is used to obtain directly from the value of a command converted to Color and is necessary for the Game Instructor . So we will open the file public/tier1/convar.h and approximately on line 352 we will find the following:

FORCEINLINE_CVAR int			GetInt( void ) const;

Y justo debajo agregamos:

FORCEINLINE_CVAR Color			GetColor( void ) const;

Now in that same file and approximately on line 430 we will find the following:

//-----------------------------------------------------------------------------
// Purpose: Return ConVar value as an int
// Output : int
//-----------------------------------------------------------------------------
FORCEINLINE_CVAR int ConVar::GetInt( void ) const 
{
	return m_pParent->m_nValue;
}

Just below we add:

//-----------------------------------------------------------------------------
// Purpose: Return ConVar value as a color
// Output : Color
//-----------------------------------------------------------------------------
FORCEINLINE_CVAR Color ConVar::GetColor( void ) const 
{
	unsigned char *pColorElement = ((unsigned char *)&m_pParent->m_nValue);
	return Color( pColorElement[0], pColorElement[1], pColorElement[2], pColorElement[3] );
}

Note: Keep in mind that "GetColor" would only work in 'ConVar', making it work in 'ConVarRef' would have to be the same process (Although it is not necessary for the Game Instructor)

Oh! And I almost forgot, it is necessary to add the definition of Color, for it in the same file just add:

#include "color.h"

At the beginning, just below the others "#include"

And that's it! Oh good at least for the Game Instructor. If you compile your Mod now you would only get access to the commands and messages in the console generated by it. Now you have to prepare it to start working...

Preparing the Game Instructor

The first thing we must do is to import the textures and certain main files so that the Game Instructor can work properly, to facilitate the task I created a small .zip that contains these files:

http://www.mediafire.com/download/8rm0xiyb3xp2lr0/GameInstructor.zip

Mirror: https://mega.co.nz/#!XQFVCBaJ!CmtqpjyjKziWgrYaJXygQHlH8g1lSzT59YC4RVZLNw8

Mirror: http://raegquit.com/beerdude26/GameInstructor(2).zip

It is a small file (144 KB) but for the distrustful... https://www.virustotal.com/en/file/fce648a7ca6ce0933243f231d36a258f561228fd15fc47063a7e056c7d86b35f/analysis/1379772975/

Here I explain the contents of the files / folders:

materials/

It contains the textures of the icons that will appear next to the message.

resource/modevents.res

Contains the events that can be called from the Server to the Client. When adding a new lesson it will be necessary to add your respective event here or else it will not work. (Contains the main events for the Game Instructor)

scripts/instructor_lessons.txt

Yep, this will be the file that will contain all the lessons that we can make appear with the Game Instructor. It is also valid to use the file mod_lessons.txt (Creating it)

scripts/mod_textures.txt

It contains the names for the icons and the texture you will use. From here you can add new icons (Of course, if you've already made your texture)


Note: Some icons in the file come from Left 4 Dead 2.

Note 2: Some files such as "mod_textures" and "mod_events" may already exist in your Mod folder, if so I recommend you "merge" the original content with the content of the .zip. Replacing the file will cause some functions to stop working.

Sources

Before creating a lesson it is necessary to configure the Fonts, that is, the type of letter that will be used in the messages and when displaying keys as icons. These were not included in the .zip since it is very possible that you have edited the file resource/ClientScheme.res. So open the file mentioned and look for the section that has something similar to this:

        //////////////////////// FONTS /////////////////////////////
	//
	// describes all the fonts
	Fonts
	{

And just below it hits the following:

                "InstructorTitle"
		{
			"1"
			{
				"name"			"Arial"
				"tall"			"20"
				"weight"		"400"
				"antialias"		"1"
				"dropshadow"	"1"
			}
		}

		"InstructorKeyBindings"
		{
			"1"
			{
				"name"			"Arial"
				"name"			"Trade Gothic Bold"
				"tall"			"18"
				"weight"		"600"
				"antialias"		"1"
				"dropshadow"	"0"
			}
		}

		"InstructorButtons"
		{
			"1"
			{
				"name"			"Arial"
				"name"			"Trade Gothic Bold"
				"tall"			"15"
				"weight"		"600"
				"antialias"		"1"
				"dropshadow"	"1"
			}
		}

		"InstructorTitleGlow"
		{
			"1"
			{
				"name"			"Arial"
				"name"			"Trade Gothic Bold"
				"tall"			"20"
				"weight"		"400"
				"antialias"		"1"
				"dropshadow"	"1"
			}
		}

As you can see, I have adjusted it to use the "Arial" typeface that is basically the most common in the universe, you can change it if you want it.

Quickly for what they serve:

InstructorTitle

This will be the type of letter that will be used in the messages.

InstructorKeyBindings

This will be the type of letter that will be used in the keys that will appear as icons. (See the image of the beginning)

InstructorButtons

To do:  What's the use?

InstructorTitleGlow

If it indicates the font for a "bright" message, however it seems that it does not work ... It is supposed to be activated with the command locator_text_glow

Creating lessons

Introductory Lesson "Using your weapon"

First we must open the file scripts / instructor_lessons.txt (That came with the .zip) And at the end of the file (But before the '}' ) we must add the lesson we want.

To begin we must decide what name we will put to this lesson, in itself it will only serve us to be able to identify it at the moment of testing and to give it a unique identification. In this case I'll call it "Using your weapon" so we'll start by writing:

"Using your weapon"
{
}

Now it is necessary to indicate what kind of lesson will be, if it indicates if the lesson can be opened several times on the screen or only once, etc... As we want to show only once we will have to define the value of instance_type 2. In addition it is necessary to adjust the priority of the lesson, for now we will leave it in 0

"Using your weapon"
{
      "priority"         "1"
      "instance_type"    "2"
}

Here the original meaning of each number:

0 = Multiple lessons of same type can be open at once 1 = Only one of each lesson type can be open at once 2 = Replace lesson of the same type 3 = Only one instance will display at a time (but all instances will be open)

After we have to indicate the message we want to give the user, keep in mind that it is possible to use the strings to translate, for example: #Instructor_UseWeapon but in this example we will use a normal message (Do not do it in your mod) , do it with the chains to translate)

"Using your weapon"
{
      "instance_type"    "2"
      "caption"          "Primary Attack"
}

As in if it is a lesson in which we will teach the player how to shoot his weapon will be necessary that the icon is a command / key, for it we will define the value of on_screen_icon as use_binding which in itself means "Use a command / key" which we will define in the field binding:

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Primary Attack"

      "onscreen_icon"    "use_binding"
      "binding"          "+attack"
}

As you can see (and if you have practiced some of this in Source Engine) you will know that the command +attack serves to indicate to the engine that the left mouse button has been pressed, so when this lesson is shown the icon will be a Mouse with the left button in red (Indicating that you must press)

As you can see in this page you can use any command. For example +jump will show the "Space" key (As you can see in the start image) and +use will show the "E" key. Of course, if the player keeps the configuration of the controls intact, if for example change the use key (E) by U the Game Instructor will display the U key.

on_screen_icon is used to indicate the icon that will appear when the target is visible to the player, in this case there is no objective so it will always appear, if there was an object (For example: a button) we should also include the field offscreen_icon which will be the icon that will appear when the target is not in sight (Along with an arrow)

Just as an example / reminder we will:

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Primary Attack"

      "onscreen_icon"    "use_binding"
      "offscreen_icon"   "icon_info"
      "binding"          "+attack"
}

If there was a target, the icon that would be displayed would be:

Hint 002 icon info.jpg

Now it is necessary to indicate how many times maximum it can appear, for this there are 2 forms: For times that it has been shown or For times that it has been fulfilled.

For times shown indicates that when the lesson has been shown a certain number of times the same will no longer appear. It is usually the simplest and is defined with the field display_limit followed by the number of times it can appear.

'Sometimes it has been fulfilled' indicates that when a condition (Generally in the code) the lesson is fulfilled it will be marked as "Met / Learned" and after a number of times "Learned" it will no longer appear . This is the one we will use with this lesson and it is defined with the field success_limit in which we will adjust it 2 times maximum.

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Primary Attack"

      "onscreen_icon"    "use_binding"
      "offscreen_icon"   "icon_info"
      "binding"          "+attack"

      "success_limit"    "2"
      "timeout"          "8"
}

As you can see we have also added the field timeout that simply defines the time in seconds that can remain open, keep in mind that if the time is finished it will be marked as "View" and it will only affect the field display_limit

Now comes the good ... it is necessary to indicate with what event this Lesson will be opened and with what other event will be marked as "Learned". In this case we will use the events: instructor_primaryattack and use_primaryattack (In case you can name them as you wish)

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Primary Attack"

      "onscreen_icon"    "use_binding"
      "offscreen_icon"   "icon_info"
      "binding"          "+attack"

      "success_limit"    "2"
      "timeout"          "8"

      "open"
      {
            "instructor_primaryattack"
            {
            }
      }

      "success"
      {
            "use_primaryattack"
            {
            }
      }
}

Ok, with this we indicate with what event will open and with what event will be marked as "Learned" but now we must tell you what parameters will be received from the event and how to use them. In this case together with the 2 events we will send the parameter userid which will indicate the user that should receive this lesson.

Remember that events are sent from the Server to the Client and these are global ie All players connected to the server will receive it, so with userid we limit the Game Instructor to only I showed it in the player that we indicated.

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Primary Attack"

      "onscreen_icon"    "use_binding"
      "offscreen_icon"   "icon_info"
      "binding"          "+attack"

      "success_limit"    "2"
      "timeout"          "8"

      "open"
      {
            "instructor_primaryattack"
            {
                 "local_player is"   "player userid"
                 "icon_target set"   "player local_player"
            }
      }

      "success"
      {
            "use_primaryattack"
            {
                 "local_player is"   "player userid"
                 "void close"        "void"
            }
      }
}

This is one of the small parts difficult to understand but here we go ...

local_player is

local_player means the "Local Player" or the Client, so with this line we indicate that only show that lesson if the local player is the player that we indicate in the value (player userid)

player userid

This is the value that each condition must have, the first part (player) indicates the type of value that is being received, in this case it is a resource "C_BasePlayer", if it were a text string it would be string instead of player and if it were an entity then it would be entity (C_BaseEntity) instead of player.

The second part (userid) indicates the name of the value, in this case we put it that way but it can be any other that you indicate. You'll see when we get to the part where we send the event from the code.

icon_target set

icon_target is the objective where the icon will be displayed, with this we simply establish (set) the object / player / entity where the icon will go.

player local_player

And with this we only say that the goal is the same player so the result will be only the message and icon showing on the screen of it.

For use_primaryattack it is basically the same only at the end after checking if the local user closes the lesson (The disappears)

And that's all for this lesson, of course there are many more fields and options to create more advanced and interactive lessons. If you have Left 4 Dead 2 I recommend you see your file instructor_lessons.txt to give you an idea. (It is located inside the VPK file: pak01_dir.vpk)

Finally it is necessary to add the events (instructor_primaryattack and use_primaryattack) to the file resource / modevents.res, open it and at the end (But before '}' ) write:

        "instructor_primaryattack"
	{
		"userid" 	"short"
	}
	"use_primaryattack"
	{
		"userid" 	"short"
	}


Feel free to incorporate new lessons into this tutorial!

Incorporating the lessons in the code

As we said before to make a lesson open (it is shown) it is necessary to send the event that we registered for it from the code, for this and taking as reference the lesson of Introduction "Using your weapon" we will do the following:

"Using your weapon"

This lesson shows the user how to shoot a weapon so the logical thing would be to show it when he obtains a weapon, for that we must go to the file 'game/server/hl2mp/hl2mp_player.cpp' ( That's right, we left Client and now we enter to Server) and we will locate the function:

void CHL2MP_Player::Spawn(void)

Note!: As we know, in Half-Life 2: Deathmatch players get weapons when they start, as an example we will show the lesson in the "Spawn" function after the player obtains the basic weapons. If your MOD does not start with weapons at the beginning, a better place to do this would be in the '"Weapon_Equip"' function (Create it)

At the end of the function we will write:

IGameEvent *pEvent = gameeventmanager->CreateEvent("instructor_primaryattack");

if ( pEvent )
{
	pEvent->SetInt("userid", GetUserID());
	gameeventmanager->FireEvent(pEvent);
}


Bug:  For some reason (Probably by the gamerules of HL2: MP) the lesson only opens when respawn and it detects the player as dead.

If we compile our Mod we can see for the first time the Game Instructor in action:

http://www.youtube.com/watch?v=pGMZyv3_5b4&hd=1