Agregando el Game Instructor

From Valve Developer Community
Jump to: navigation, search
El "Game Instructor" permite mostrar instrucciones mientras se juega.

El Game Instructor en breves palabras es un sistema ubicado en el Cliente enfocado para mostrar instrucciones sobre como jugar o realizar ciertas acciones mientras al mismo tiempo se juega. El mismo apareció por primera vez en el Branch de Alien Swarm (o hasta donde imagino, puedo estar equivocado) y actualmente es usado por los juegos de Valve.

En este articulo veremos a como incorporarlo dentro del Source SDK 2013 Multiplayer ( No he probado si funciona dentro del Branch para Singleplayer o en Source SDK 2007 ). El código que usaremos es proveniente del código fuente de Alien Swarm con algunas modificaciones para hacerlo funcionar dentro del Source SDK 2013.

To do: Completar el tutorial...

Requisitos

CLocatorTarget

La clase CLocatorTarget de los archivos hud_locator_target.cpp/hud_locator_target.h es la encargada de mostrar los mensajes en la pantalla del jugador ( Cliente ) y ( en caso de dirigirse a un objeto, por ejemplo un botón ) de mover el mismo hacia la ubicación del objeto a usar ( De ahí proviene su nombre ). Así que para empezar necesitaremos incorporar los siguientes 2 archivos dentro del proyecto del Cliente:

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

Ahora es necesario editar el archivo utlsymbol.h que se encuentra en public/tier1/utlsymbol.h, una vez que hayamos abierto el archivo eliminamos TODO su contenido y pegamos el nuevo código:

//========= 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

Para ser honesto no entiendo mucho acerca de este archivo y para que sirven exactamente las actualizaciones hechas al mismo, sin embargo son necesarios para hud_locator_target.

C_GameInstructor y CBaseLesson

Ahora pasaremos al sistema real del Game Instructor, este se encarga de leer 2 archivos ( scripts/instructor_lessons.txt y scripts/mod_lessons.txt ) que son los responsables de tener las "lecciones por aprender" que básicamente son los eventos que el Game Instructor puede mostrar en la pantalla junto a las opciones que van desde el icono que aparecerá junto al mensaje hasta cuando se marcará como "lección aprendida" o "lección vista".

Se puede decir que es como un tipo de lenguaje que nos permite crear lecciones personalizadas que pueden "aprenderse" o sencillamente cerrarse cuando cierta condición se cumple dentro del código, algo complicado de entender al principio pero veremos una introducción de como crearlos más adelante, por ahora pasemos al código de este sistema:

CBaseLesson

En resumen es la clase encargada de contener toda la información acerca de cada lección que puede aparecer con el Game Instructor. Tendremos que agregar estos 2 archivos igual en el Cliente:

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

Y ahora si, esta clase es el Game Instructor encargada de leer los archivos que contienen las lecciones por aprender, guardarlas, actualizar la información, mostrarlas, etc... Al igual que antes, debemos agregar estos 2 archivos en el Cliente:

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();
}

Arreglando problemas

El Game Instructor ya ha sido incorporado sin embargo es necesario ahora arreglar ciertos problemas con los demás archivos.

UTIL_CountNumBitsSet

Esta función es necesaria para el funcionamiento de las lecciones y debería estar en el archivo game/shared/util_shared.cpp por lo que abriremos tal archivo y buscaremos la función UTIL_StringFieldToInt aproximadamente en la línea 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;
}

Y justo debajo de ella incorporaremos este código:

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;
}

Y ahora como es lógico definiremos esas 2 funciones dentro de game/shared/util_shared.h, como antes buscaremos la definición de la función UTIL_StringFieldToInt que se encuentra aproximadamente en la línea 587:

int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings );

Y justo debajo de ella agregamos:

int UTIL_CountNumBitsSet( unsigned int nVar );
int UTIL_CountNumBitsSet( uint64 nVar );

GetPlayerName

Esta función como bien dice, sirve para obtener el nombre de un jugador, sin embargo en el Source SDK 2013 la misma solo es accesible desde CBasePlayer mientras que el Game Instructor lo requiere en CBaseEntity. Por lo que abriremos el archivo game/client/c_baseentity.h y deberemos agregar esta función en alguna sección accesible públicamente "public:", en mi caso la he puesto debajo de GetDebugName que se encuentra aproximadamente en la línea 860:

char const						*GetDebugName( void );

Ahora debajo de ella agregamos:

virtual const char				*GetPlayerName() const { return NULL; }

CHudIcons

Esta clase es la encargada de mostrar los iconos que se encuentran al lado del mensaje cuando una lección aparece en la pantalla y la misma no existe en el Source SDK 2013. Para agregarla deberemos abrir el archivo game/client/hud.h y aproximadamente en la línea 195 encontraremos lo siguiente:

extern CHud gHUD;

Ahora debajo de ella agregamos la clase:

//-----------------------------------------------------------------------------
// 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();

Ahora abrimos el archivo game/client/hud.cpp y al final de todo el archivo es decir en la línea 1200 agregamos lo siguiente:

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;
}

Como se puede ver en el código anterior los iconos se definirán en los archivos: scripts/hud_textures.txt, scripts/mod_textures.txt y scripts/instructor_textures.txt

Aunque eso no es todo, ya hemos agregado la clase pero ahora es necesario iniciarla/prepararla, para ello en ese mismo archivo ( hud.cpp ) buscaremos la función CHud::Init que se encuentra aproximadamente en la línea 395 y al final de la función ( Debajo de "FreeHudTextureList( textureList );" ) agregamos lo siguiente:

HudIcons().Init();

GetColor

El código para los comandos de consola "ConVar" tiene una nueva función llamada GetColor la cual como su nombre lo indica sirve para obtener directamente del valor de un comando convertido en Color y es necesario para el Game Instructor. Así que abriremos el archivo public/tier1/convar.h y aproximadamente en la línea 352 encontraremos lo siguiente:

FORCEINLINE_CVAR int			GetInt( void ) const;

Y justo debajo agregamos:

FORCEINLINE_CVAR Color			GetColor( void ) const;

Ahora en ese mismo archivo y aproximadamente en la línea 430 encontraremos lo siguiente:

//-----------------------------------------------------------------------------
// Purpose: Return ConVar value as an int
// Output : int
//-----------------------------------------------------------------------------
FORCEINLINE_CVAR int ConVar::GetInt( void ) const 
{
	return m_pParent->m_nValue;
}

Justo debajo agregamos:

//-----------------------------------------------------------------------------
// 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] );
}

Nota: Ten en cuenta que "GetColor" solo funcionaría en 'ConVar', hacerlo funcionar en 'ConVarRef' tendría que ser el mismo proceso ( Aunque no es necesario para el Game Instructor )

¡Oh! Y casi lo olvido, es necesario agregar la definición de Color, para ello en el mismo archivo solo agrega:

#include "color.h"

Al inicio, justo debajo de los demás "#include"

¡Y eso es todo! Oh bueno al menos para el Game Instructor. Si compilas tu Mod ahora solo obtendrías acceso a los comandos y a los mensajes en la consola generados por el mismo. Ahora hay que prepararlo para que empiece a funcionar...

Preparando al Game Instructor

Lo primero que debemos hacer es importar las texturas y ciertos archivos principales para que el Game Instructor pueda funcionar correctamente, para facilitar la tarea he creado un pequeño .zip que contiene estos archivos:

http://www.mediafire.com/download/8rm0xiyb3xp2lr0/GameInstructor.zip

Mirror: https://mega.co.nz/#!XQFVCBaJ!CmtqpjyjKziWgrYaJXygQHlH8g1lSzT59YC4RVZLNw8

Mirror: http://raegquit.com/beerdude26/GameInstructor(2).zip

Es un archivo pequeño ( 144 KB ) pero para los desconfiados... https://www.virustotal.com/en/file/fce648a7ca6ce0933243f231d36a258f561228fd15fc47063a7e056c7d86b35f/analysis/1379772975/

Aquí explico el contenido de los archivos/carpetas:

materials/

Contiene las texturas de los iconos que aparecerán al lado del mensaje.

resource/modevents.res

Contiene los eventos que pueden ser llamados del Servidor al Cliente. Al agregar una nueva lección será necesario agregar su respectivo evento aquí o de lo contrario no funcionara. ( Contiene los eventos principales para el Game Instructor )

scripts/instructor_lessons.txt

Yep, este será el archivo que contendrá todas las lecciones que podemos hacer aparecer con el Game Instructor. También es válido usar el archivo mod_lessons.txt ( Creandolo )

scripts/mod_textures.txt

Contiene los nombres para los iconos y la textura que usará. Desde aquí puedes agregar nuevos iconos (Claro, si ya has hecho su textura)


Nota: Algunos iconos del archivo provienen de Left 4 Dead 2.

Nota 2: Algunos archivos como "mod_textures" y "mod_events" podrían ya existir en la carpeta de tu Mod, si es así te recomiendo "fusionar" el contenido original con el contenido del .zip. Reemplazar el archivo hará que algunas funciones dejen de funcionar.

Fuentes

Antes de crear una lección es necesario configurar las Fuentes, es decir, el tipo de letra que se usará en los mensajes y a la hora de mostrar teclas como iconos. Estos no se incluyeron en el .zip ya que es muy posible que hayas editado el archivo resource/ClientScheme.res. Así que abre el archivo mencionado y busca la sección que tiene algo similar a esto:

        //////////////////////// FONTS /////////////////////////////
	//
	// describes all the fonts
	Fonts
	{

Y justo debajo pega lo siguiente:

                "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"
			}
		}

Como puedes ver la he ajustado para que use el tipo de letra "Arial" que básicamente es la más común en todo el universo, puedes cambiarla si lo quieres.

Rápidamente para lo que sirven:

InstructorTitle

Este sera el tipo de letra que se usará en los mensajes.

InstructorKeyBindings

Este sera el tipo de letra que se usará en las teclas que aparecerán como iconos. ( Véase la imagen del inicio )

InstructorButtons

To do: ¿Para que sirve?

InstructorTitleGlow

En si indica el tipo de letra para un mensaje "brillante", sin embargo parece que no funciona... Se supone que se activa con el comando locator_text_glow

Creando lecciones

Lección de Introducción. "Usando tu arma"

Primero debemos abrir el archivo scripts/instructor_lessons.txt ( Que venia con el .zip ) Y al final del archivo ( Pero antes de la } ) debemos agregar la lección que queremos.

Para comenzar debemos decidir que nombre le pondremos a esta lección, en si solo nos servirá para poder identificarlo al momento de probar y de darle una identificación única. En este caso lo llamare "Usando tu arma" así que empezaremos por escribir:

"Usando tu arma"
{
}

Ahora es necesario indicarle que tipo de lección será, en si le indica si la lección podrá abrirse varias veces en la pantalla o solo una vez, etc... Como queremos que solo se muestre una vez tendremos que definir el valor de instance_type como 2. Además es necesario ajustar la prioridad de la lección, por ahora la dejaremos en 0

"Usando tu arma"
{
      "priority"         "1"
      "instance_type"    "2"
}

Aquí el significado original de cada número:

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)

Después tendremos que indicarle el mensaje que queremos darle al usuario, ten en cuenta que es posible usar las cadenas para traducir, por ejemplo: #Instructor_UseWeapon pero en este ejemplo usaremos un mensaje normal ( No lo hagas en tu mod, hazlo con las cadenas para traducir )

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Disparo primario"
}

Como en si se trata de una lección en la que le enseñaremos al jugador a como disparar su arma será necesario que el icono sea un comando/tecla, para ello definiremos el valor de on_screen_icon como use_binding que en si significa "Usa un comando/tecla" la cual le definiremos en el campo binding:

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Disparo primario"

      "onscreen_icon"    "use_binding"
      "binding"          "+attack"
}

Como puedes observar ( Y si has practicado algo de esto en Source Engine ) sabrás que el comando +attack sirve para indicar al motor que el botón izquierdo del ratón a sido presionado, por lo que cuando esta lección sea mostrada el icono será un Ratón con el botón izquierdo en rojo ( Indicando que se debe presionar )

Como se puede ver en esta página se puede usar cualquier comando. Por ejemplo +jump mostrará la tecla "Espacio" ( Como se puede ver en la imagen del inicio ) y +use mostrará la tecla "E". Claro esta, si el jugador mantiene la configuración de los controles intacta, si por ejemplo cambio la tecla de uso (E) por U el Game Instructor mostrará la tecla U.

on_screen_icon sirve para indicar el icono que aparecerá cuando el objetivo este visible al jugador, en este caso no hay objetivo por lo que siempre aparecerá, si hubiera un objeto ( Por ejemplo: un botón ) también deberíamos incluir el campo offscreen_icon que será el icono que aparecerá cuando el objetivo no este a la vista ( Junto con una flecha )

Solo como ejemplo/recordatorio lo pondremos:

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Disparo primario"

      "onscreen_icon"    "use_binding"
      "offscreen_icon"   "icon_info"
      "binding"          "+attack"
}

Si hubiera un objetivo el icono que se mostraría sería:

Hint 002 icon info.jpg

Ahora es necesario indicar cuantas veces máximo puede aparecer, para esto hay 2 formas: Por veces que se ha mostrado o Por veces que se ha cumplido.

Por veces que se ha mostrado indica que cuando la lección se haya mostrado cierta cantidad de veces el mismo Ya no volverá a aparecer. Suele ser la más sencilla y se define con el campo display_limit seguido del número de veces que puede aparecer.

Por veces que se ha cumplido indica que cuando una condición ( Generalmente en el código ) se cumpla la lección se marcará como "Cumplida/Aprendida" y después de un número de veces "Aprendida" la misma dejará de aparecer. Esta es la usaremos con esta lección y es definida con el campo success_limit en la cual le ajustaremos 2 veces máximo.

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Disparo primario"

      "onscreen_icon"    "use_binding"
      "offscreen_icon"   "icon_info"
      "binding"          "+attack"

      "success_limit"    "2"
      "timeout"          "8"
}

Como puedes ver también hemos agregado el campo timeout que sencillamente define el tiempo en segundos que puede permanecer abierto, ten en cuenta que si el tiempo se termina se marcará como "Vista" y solo afectará al campo display_limit

Ahora viene lo bueno... es necesario indicarle con que evento se Abrirá esta Lección y con que otro evento se marcará como "Aprendido". En este caso usaremos los eventos: instructor_primaryattack y use_primaryattack ( En si puedes nombrarlos como quieras )

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Disparo primario"

      "onscreen_icon"    "use_binding"
      "offscreen_icon"   "icon_info"
      "binding"          "+attack"

      "success_limit"    "2"
      "timeout"          "8"

      "open"
      {
            "instructor_primaryattack"
            {
            }
      }

      "success"
      {
            "use_primaryattack"
            {
            }
      }
}

Vale, con esto le indicamos con que evento se abrirá y con que evento se marcará como "Aprendida" pero ahora hay que decirle que parámetros se recibirán del evento y como usarlos. En este caso junto con los 2 eventos enviaremos el parámetro userid que indicará el usuario que debe recibir esta lección.

Hay que recordar que los eventos son enviados del Servidor al Cliente y estos son globales es decir Todos los jugadores conectados al servidor lo recibirán por lo que con userid limitamos al Game Instructor a que solo lo muestre en el jugador que le indicamos.

"Usando tu arma"
{
      "instance_type"    "2"
      "caption"          "Disparo primario"

      "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"
            }
      }
}

Esta es una de las pequeñas partes difíciles de entender pero aquí vamos...

local_player is

local_player significa el "Jugador local" o en si el Cliente, por lo que con esta línea le indicamos que solo muestre esa lección si el jugador local es el jugador que le indicamos en el valor ( player userid )

player userid

Este es el valor que cada condición debe tener, la primera parte ( player ) indica el tipo de valor que se esta recibiendo, en este caso es un recurso "C_BasePlayer", si fuera una cadena de texto sería string en vez de player y si fuera una entidad entonces sería entity ( C_BaseEntity ) en vez de player.

La segunda parte ( userid ) indica el nombre del valor, en este caso le pusimos así pero puede ser cualquier otro que tu le indiques. Ya lo veras cuando lleguemos a la parte donde enviemos el evento desde el código.

icon_target set

icon_target es el objetivo donde se mostrará el icono, con esto sencillamente le establecemos ( set ) el objeto/jugador/entidad a donde ira el icono.

player local_player

Y con esto solo le decimos que el objetivo es el mismo jugador por lo que el resultado será solo el mensaje e icono mostrándose en la pantalla del mismo.

Para use_primaryattack es básicamente lo mismo solo que al final después de comprobar si es el usuario local cierra la lección ( La desaparece )

Y eso es todo para esta lección, claro que hay muchos más campos y opciones para crear lecciones más avanzadas e interactivas. Si cuentas con Left 4 Dead 2 te recomiendo ver su archivo instructor_lessons.txt para darte una idea. ( Se encuentra dentro del archivo VPK: pak01_dir.vpk )

Por último es necesario agregar los eventos ( instructor_primaryattack y use_primaryattack ) al archivo resource/modevents.res, ábrelo y al final ( Pero antes de } ) escribe:

        "instructor_primaryattack"
	{
		"userid" 	"short"
	}
	"use_primaryattack"
	{
		"userid" 	"short"
	}


¡Siéntente libre de incorporar nuevas lecciones a este tutorial!

Incorporando las lecciones en el código

Como dijimos anteriormente para hacer que una lección se abra ( se muestre ) es necesario enviar el evento que registramos para el mismo desde el código, para ello y tomando como referencia la lección de Introducción "Usando tu arma" haremos lo siguiente:

"Usando tu arma"

Esta lección le muestra al usuario a como disparar un arma por lo que lo lógico sería que se mostrará cuando el mismo obtiene un arma, para ello hay que dirigirnos al archivo game/server/hl2mp/hl2mp_player.cpp ( Así es, dejamos Cliente y ahora entramos a Server ) y ubicaremos la función:

void CHL2MP_Player::Spawn(void)

¡Nota!: Como sabemos, en Half-Life 2: Deathmatch los jugadores obtienen armas al empezar, como ejemplo mostraremos la lección en la función "Spawn" después de que el jugador obtiene las armas básicas. Si tu MOD no empieza con armas al empezar, un mejor lugar para hacer esto sería en la función "Weapon_Equip" ( Hay que crearla )

Al final de la función escribiremos:

IGameEvent *pEvent = gameeventmanager->CreateEvent("instructor_primaryattack");

if ( pEvent )
{
	pEvent->SetInt("userid", GetUserID());
	gameeventmanager->FireEvent(pEvent);
}


Bug: Por alguna razón (Probablemente por las reglas de juego de HL2:MP) la lección solo se abre al renacer y el mismo detecta al jugador como muerto.

Si compilamos nuestro Mod podremos ver por primera vez el Game Instructor en acción:

http://www.youtube.com/watch?v=pGMZyv3_5b4&hd=1