Adding the Game Instructor: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
(Better formatting, the code is now in a github repository and mirrors.)
Line 1: Line 1:
{{otherlang2|es=Adding_the_Game_Instructor:es}}
{{otherlang2|es=Adding_the_Game_Instructor:es}}


[[File:Hint_017_show_key_bindings.jpg‎‎‎|right|450px|thumb|The "Game Instructor" allows you to display instructions while playing.]]
[[File:Source-2013-instructor-example.jpg|thumb|right|The Game Instructor on Source SDK 2013.]]


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


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


{{TODO|Complete the tutorial...}}
{{TODO|download links for materials are down, try to add a new link with the required files}}
== Requirements ==
== Requirements ==


* A Mod based on the [[Source SDK 2013]]
* A mod based on the [[Source SDK 2013]]
* Knowledge in C++
* Knowledge in C++


== CLocatorTarget ==
== Source Code and Assets ==


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


{{Hud_locator_target}}
https://github.com/kolessios/source-instructor


== UtlSymbol ==
'''Mirror:'''


Now it is necessary to edit the file '''utlsymbol.h''' that is in '''publi /tier1/utlsymbol.h''', once we have opened the file we eliminate '''TODO''' its content and paste the new code:
https://www.dreamlink.cloud/explorer?cid=QmbRA6eiT4eaNbpvJqY5QDPm1XStx3FGL3HJK2YTmQCc5M&filename=source-instructor.zip


{{Instructor_UtlSymbol}}
'''Mirror:'''


To be honest, I do not understand much about this file and exactly what updates are made to it, however they are necessary for ''hud_locator_target''.
https://bafybeihcgbejsltrs2h52o7deaz4pm2ojirug2fzrko5xbm66mrsqdhtze.ipfs.dweb.link/explorer?cid=QmbRA6eiT4eaNbpvJqY5QDPm1XStx3FGL3HJK2YTmQCc5M&filename=source-instructor.zip


== C_GameInstructor and CBaseLesson ==
'''Mirror:'''


Now we will move to the real system of the Game Instructor, this is responsible for reading 2 files (''scripts/instructor_lessons.txt'' and ''scripts /mod_lessons.txt'') that are responsible for having the "lessons to be learned" that basically they are the events that the Game Instructor can show on the screen next to the options that go from the icon that will appear next to the message until when it will be marked as "lesson learned" or "lesson seen".
https://bafybeihcgbejsltrs2h52o7deaz4pm2ojirug2fzrko5xbm66mrsqdhtze.ipfs.infura-ipfs.io/explorer?cid=QmbRA6eiT4eaNbpvJqY5QDPm1XStx3FGL3HJK2YTmQCc5M&filename=source-instructor.zip


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


=== CBaseLesson ===
# Clone or download the files from the repository. (Links above)
# Add the files from the <code>src</code> folder to your mod code.
# Add the files from the <code>game</code> folder to the files/assets of your mod, if these files already exist it is '''highly recommended to merge rather than replace'''.
# Apply the '''fixes''' (below) to your mod code.
# Optional but recommended, add the contents of <code>game/instructor.fgd</code> to the fgd of your mod to trigger lessons from the map editor.


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


{{CBaseLesson}}
Lessons should be added in <code>game/scripts/instructor_lessons.txt</code>.


=== C_GameInstructor ===
The current file has an example lesson to show the button that has to be pressed to fire a weapon:


And now, this class is the Game Instructor in charge of reading the files that contain the lessons to be learned, saving them, updating the information, showing them, etc... As before, we must add these 2 files in the Client:
<pre>
"Primary Attack"
{
  "instance_type"    "2"
  "caption"          "Press to shoot"
 
  "onscreen_icon"    "use_binding"
  "offscreen_icon"  "icon_info"
  "binding"          "+attack"
 
  "success_limit"    "2"
  "timeout"          "8"
 
  "open"
  {
    // Open when the code fires this event.
    // Example: Player has picked up a weapon.
    "instructor_primaryattack"
    {
      "local_player is"  "player userid"
      "icon_target set"  "player local_player"
    }
  }
 
  "success"
  {
    // Tutorial successfully completed when the code fires this event.
    // Example: Player has pressed the primary mouse button.
    "use_primaryattack"
    {
      "local_player is"  "player userid"
      "void close"        "void"
    }
  }
}
</pre>
 
Since there is no documentation on how to create lessons it is recommended to see the <code>instructor_lessons.txt</code> of Left 4 Dead and Alien Swarm.


{{C_GameInstructor}}
== Triggering lessons ==


== Fixing problems ==
The easiest way is to use the <code>env_instructor_hint</code> entity in Hammer, from there you can configure in a user friendly way the icon, text and other basic options.


The Game Instructor has already been incorporated however it is now necessary to fix certain problems with the other files.
For more advanced things it will be necessary to use events triggered from the code. The example above (on how to fire a weapon) uses the <code>instructor_primaryattack</code> event to display the lesson and the <code>use_primaryattack</code> event to set it as completed.


=== UTIL_CountNumBitsSet ===
Events must be added to <code>game/resource/modevents.res</code> before they can be used in code.


This function is necessary for the operation of the lessons and should be in the file '''game/shared/util_shared.cpp''' so we will open such file and look for the function '''UTIL_StringFieldToInt''' approximately on the line ''1089'':
Now you can launch the event with the following code:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings )
IGameEvent *pEvent = gameeventmanager->CreateEvent("instructor_primaryattack");
 
if (pEvent)
{
{
if ( !szValue || !szValue[0] )
    pEvent->SetInt("userid", GetUserID());
return -1;
    gameeventmanager->FireEvent(pEvent);
}
</syntaxhighlight>
 
You can get more information about [[Networking Events & Messages|events here]].
 
== Fixes ==


for ( int i = 0; i < iNumStrings; i++ )
For the repository code to work you need to make some changes to the existing code of your mod:
{
if ( FStrEq(szValue, pValueStrings[i]) )
return i;
}


Assert(0);
=== <code>game/shared/util_shared.cpp</code> ===
return -1;
}
</syntaxhighlight>


And '''just below it''' we will incorporate this code:
Look for <code>UTIL_StringFieldToInt</code> and under it add the following:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 127: Line 167:
</syntaxhighlight>
</syntaxhighlight>


And now, as is logical, we will define those 2 functions inside '''game/shared/util_shared.h''', as before we will look for the definition of the function '''UTIL_StringFieldToInt''' which is approximately on the line ''587'':
=== <code>game/shared/util_shared.h</code> ===
 
<syntaxhighlight lang="cpp">
int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings );
</syntaxhighlight>


And '''just below it''' we add:
Look for <code>UTIL_StringFieldToInt</code> and under it add the following:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 140: Line 176:
</syntaxhighlight>
</syntaxhighlight>


=== GetPlayerName ===
=== <code>game/client/c_baseentity.h</code> ===


This function, as it says, serves to obtain the name of a player, however in the Source SDK 2013 it is only accessible from ''CBasePlayer'' while the Game Instructor requires it in ''CBaseEntity''. So we will open the file '''game/client/c_baseentity.h''' and we will add this function in some publicly accessible section "public:", in my case I have put it under '''GetDebugName''' that is approximately on line ''860'':
Look for <code>GetDebugName</code> and under it add the following:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
char const *GetDebugName( void );
virtual const char *GetPlayerName() const { return NULL; }
</syntaxhighlight>
</syntaxhighlight>


Now '''below it''' we add:
=== <code>game/client/hud.h</code> ===
 
<syntaxhighlight lang="cpp">
virtual const char *GetPlayerName() const { return NULL; }
</syntaxhighlight>


=== CHudIcons ===
Look for <code>extern CHud gHUD;</code> and under it add the following:
 
This class is responsible for displaying the icons next to the message when a lesson appears on the screen and it does not exist in the Source SDK 2013. To add it we must open the file "game/client/hud.h" 'and approximately on line' "195" 'we will find the following:
 
<syntaxhighlight lang="cpp">
extern CHud gHUD;
</syntaxhighlight>
 
Now '''below it''' we add the class:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 197: Line 221:
</syntaxhighlight>
</syntaxhighlight>


Now we open the file '''game/client/hud.cpp''' and the final '''of the whole file''' that is to say in the line ''1200'' add the following:
=== <code>game/client/hud.cpp</code> ===
 
At the end of the file add:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 360: Line 386:


LoadHudTextures( textureList, "scripts/instructor_textures", NULL );
LoadHudTextures( textureList, "scripts/instructor_textures", NULL );
 
  LoadHudTextures( textureList, "scripts/instructor_modtextures", NULL );


// fix up all the texture icons first
// fix up all the texture icons first
Line 427: Line 453:
</syntaxhighlight>
</syntaxhighlight>


As you can see in the previous code the icons will be defined in the files: scripts/hud_textures.txt, scripts/mod_textures.txt and scripts/instructor_textures.txt
In the same file, look for the function <code>CHud::Init</code> and inside at the end add:
 
Although that is not all, we have already added the class but now it is necessary to start / prepare it, for that '''in that same file''' (hud.cpp) we will look for the function '''CHud::Init''' that is approximately on the line ''395'' and '''at the end of the function''' (Under "FreeHudTextureList (textureList);") we add the following:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 435: Line 459:
</syntaxhighlight>
</syntaxhighlight>


=== GetColor ===
=== <code>public/tier1/convar.h</code> ===
 
The code for the console commands "ConVar" has a new function called ''GetColor'' which, as its name indicates, is used to obtain directly from the value of a command converted to ''Color'' and is necessary for the Game Instructor . So we will open the file '''public/tier1/convar.h''' and approximately on line ''352'' we will find the following:
 
<syntaxhighlight lang="cpp">
FORCEINLINE_CVAR int GetInt( void ) const;
</syntaxhighlight>


Y '''justo debajo''' agregamos:
Look for <code>GetInt( void ) const;</code> and under it add the following:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
FORCEINLINE_CVAR Color GetColor( void ) const;
FORCEINLINE_CVAR Color GetColor( void ) const;
</syntaxhighlight>
</syntaxhighlight>


Now in that same file and approximately on line ''430'' we will find the following:
In the same file, look for:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 462: Line 480:
</syntaxhighlight>
</syntaxhighlight>


'''Just below''' we add:
and under it add the following:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
Line 476: Line 494:
</syntaxhighlight>
</syntaxhighlight>


Note: Keep in mind that "GetColor" would only work in 'ConVar', making it work in 'ConVarRef' would have to be the same process (Although it is not necessary for the Game Instructor)
To finish, add the following below the <code>#include</code> at the beginning of the file:
 
Oh! And I almost forgot, it is necessary to add the definition of ''Color'', for it in the same file just add:


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
#include "color.h"
#include "color.h"
</syntaxhighlight>
</syntaxhighlight>
At the beginning, just below the others "#include"
'''And that's it!''' Oh good at least for the Game Instructor. If you compile your Mod now you would only get access to the commands and messages in the console generated by it. Now you have to prepare it to start working...
== Preparing the Game Instructor ==
The first thing we must do is to import the textures and certain main files so that the Game Instructor can work properly, to facilitate the task I created a small ''.zip'' that contains these files:
http://www.mediafire.com/download/8rm0xiyb3xp2lr0/GameInstructor.zip
Mirror: https://mega.co.nz/#!XQFVCBaJ!CmtqpjyjKziWgrYaJXygQHlH8g1lSzT59YC4RVZLNw8
Mirror: http://raegquit.com/beerdude26/GameInstructor(2).zip
It is a small file (144 KB) but for the distrustful...
https://www.virustotal.com/en/file/fce648a7ca6ce0933243f231d36a258f561228fd15fc47063a7e056c7d86b35f/analysis/1379772975/
Here I explain the contents of the files / folders:
'''materials/'''
It contains the textures of the icons that will appear next to the message.
'''resource/modevents.res'''
Contains the events that can be called from the Server to the Client. When adding a new lesson it will be necessary to add your respective event here or else it will not work. (Contains the main events for the Game Instructor)
'''scripts/instructor_lessons.txt'''
Yep, this will be the file that will contain all the lessons that we can make appear with the Game Instructor. It is also valid to use the file mod_lessons.txt (Creating it)
'''scripts/mod_textures.txt'''
It contains the names for the icons and the texture you will use. From here you can add new icons (Of course, if you've already made your texture)
Note: Some icons in the file come from [[Left 4 Dead 2]].
Note 2: Some files such as "mod_textures" and "mod_events" may already exist in your Mod folder, if so I recommend you "merge" the original content with the content of the .zip. Replacing the file will cause some functions to stop working.
=== Sources ===
Before creating a lesson it is necessary to configure the Fonts, that is, the type of letter that will be used in the messages and when displaying keys as icons. These were not included in the .zip since it is very possible that you have edited the file '''resource/ClientScheme.res'''. So open the file mentioned and look for the section that has something similar to this:
<pre>
        //////////////////////// FONTS /////////////////////////////
//
// describes all the fonts
Fonts
{
</pre>
And just below it hits the following:
<pre>
                "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"
}
}
</pre>
As you can see, I have adjusted it to use the "Arial" typeface that is basically the most common in the universe, you can change it if you want it.
Quickly for what they serve:
'''InstructorTitle'''
This will be the type of letter that will be used in the messages.
'''InstructorKeyBindings'''
This will be the type of letter that will be used in the keys that will appear as icons. (See the image of the beginning)
'''InstructorButtons'''
{{TODO | What's the use?}}
'''InstructorTitleGlow'''
If it indicates the font for a "bright" message, however it seems that it does not work ... It is supposed to be activated with the command ''locator_text_glow''
== Creating lessons ==
{{InstructorLessons}}
== Incorporating the lessons in the code ==
As we said before to make a lesson open (it is shown) it is necessary to send the event that we registered for it from the code, for this and taking as reference the lesson of Introduction "Using your weapon" we will do the following:
=== "Using your weapon" ===
This lesson shows the user how to shoot a weapon so the logical thing would be to show it when he obtains a weapon, for that we must go to the file '' 'game/server/hl2mp/hl2mp_player.cpp''' ( That's right, we left Client and now we enter to Server) and we will locate the function:
<syntaxhighlight lang="cpp">
void CHL2MP_Player::Spawn(void)
</syntaxhighlight>
'''Note!''': As we know, in Half-Life 2: Deathmatch players get weapons when they start, as an example we will show the lesson in the "Spawn" function after the player obtains the basic weapons. If your MOD does not start with weapons at the beginning, a better place to do this would be in the '' '"Weapon_Equip"' '' function (Create it)
At the end of the function we will write:
<syntaxhighlight lang="cpp">
IGameEvent *pEvent = gameeventmanager->CreateEvent("instructor_primaryattack");
if ( pEvent )
{
pEvent->SetInt("userid", GetUserID());
gameeventmanager->FireEvent(pEvent);
}
</syntaxhighlight>
{{Bug | For some reason (Probably by the gamerules of HL2: MP) the lesson only opens when respawn and it detects the player as dead.}}
If we compile our Mod we can see for the first time the Game Instructor in action:
http://www.youtube.com/watch?v=pGMZyv3_5b4&hd=1


[[Category:Programming]]
[[Category:Programming]]
[[Category:Tutorials]]
[[Category:Tutorials]]

Revision as of 23:15, 14 July 2021

Template:Otherlang2

The Game Instructor on Source SDK 2013.

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

This tutorial will be going over on how to implement the Alien Swarm Game Instructor in Source SDK 2013 (it may work on other branches, but none other has been tested).

Requirements

Source Code and Assets

Github:

https://github.com/kolessios/source-instructor

Mirror:

https://www.dreamlink.cloud/explorer?cid=QmbRA6eiT4eaNbpvJqY5QDPm1XStx3FGL3HJK2YTmQCc5M&filename=source-instructor.zip

Mirror:

https://bafybeihcgbejsltrs2h52o7deaz4pm2ojirug2fzrko5xbm66mrsqdhtze.ipfs.dweb.link/explorer?cid=QmbRA6eiT4eaNbpvJqY5QDPm1XStx3FGL3HJK2YTmQCc5M&filename=source-instructor.zip

Mirror:

https://bafybeihcgbejsltrs2h52o7deaz4pm2ojirug2fzrko5xbm66mrsqdhtze.ipfs.infura-ipfs.io/explorer?cid=QmbRA6eiT4eaNbpvJqY5QDPm1XStx3FGL3HJK2YTmQCc5M&filename=source-instructor.zip

Installation

  1. Clone or download the files from the repository. (Links above)
  2. Add the files from the src folder to your mod code.
  3. Add the files from the game folder to the files/assets of your mod, if these files already exist it is highly recommended to merge rather than replace.
  4. Apply the fixes (below) to your mod code.
  5. Optional but recommended, add the contents of game/instructor.fgd to the fgd of your mod to trigger lessons from the map editor.

Adding lessons

Lessons should be added in game/scripts/instructor_lessons.txt.

The current file has an example lesson to show the button that has to be pressed to fire a weapon:

"Primary Attack"
{
  "instance_type"    "2"
  "caption"          "Press to shoot"

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

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

  "open"
  {
    // Open when the code fires this event.
    // Example: Player has picked up a weapon.
    "instructor_primaryattack"
    {
      "local_player is"   "player userid"
      "icon_target set"   "player local_player"
    }
  }

  "success"
  {
    // Tutorial successfully completed when the code fires this event.
    // Example: Player has pressed the primary mouse button.
    "use_primaryattack"
    {
      "local_player is"   "player userid"
      "void close"        "void"
    }
  }
}

Since there is no documentation on how to create lessons it is recommended to see the instructor_lessons.txt of Left 4 Dead and Alien Swarm.

Triggering lessons

The easiest way is to use the env_instructor_hint entity in Hammer, from there you can configure in a user friendly way the icon, text and other basic options.

For more advanced things it will be necessary to use events triggered from the code. The example above (on how to fire a weapon) uses the instructor_primaryattack event to display the lesson and the use_primaryattack event to set it as completed.

Events must be added to game/resource/modevents.res before they can be used in code.

Now you can launch the event with the following code:

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

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

You can get more information about events here.

Fixes

For the repository code to work you need to make some changes to the existing code of your mod:

game/shared/util_shared.cpp

Look for UTIL_StringFieldToInt and under it add the following:

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

game/shared/util_shared.h

Look for UTIL_StringFieldToInt and under it add the following:

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

game/client/c_baseentity.h

Look for GetDebugName and under it add the following:

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

game/client/hud.h

Look for extern CHud gHUD; and under it add the following:

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

game/client/hud.cpp

At the end of the file add:

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 );
  LoadHudTextures( textureList, "scripts/instructor_modtextures", 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;
}

In the same file, look for the function CHud::Init and inside at the end add:

HudIcons().Init();

public/tier1/convar.h

Look for GetInt( void ) const; and under it add the following:

FORCEINLINE_CVAR Color GetColor( void ) const;

In the same file, look for:

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

and under it add the following:

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

To finish, add the following below the #include at the beginning of the file:

#include "color.h"