The Place to Start


Class System w/ additional features 
rkzad
Intermediate


Class System w/ additional features
Previous code
New code

Tutorial in brief:
1) How to implement classes
2) How to do 2 type of observer modes before selecting a class.
3) How to make menus and select them
4) Using Titles.txt for menus
5) Using the console to access menus and functions
6) Show Player Info (additional function)
7) Show Class Info (additional function)
8) Modifying the controls section to call our functions
Well, here's my first (somewhat basic however though) tutorial. Hope this goes well. So this tutorial is a basic class sytem (2 classes to choose from) with additional features that you'll see later. The first thing we're going to do is open player.h (line 72):


class CBasePlayer : public CBaseMonster
{
public:
	// Start modification code
	int					m_iPlayerClass;// player's present class
	int					m_iFutureClass;// player's future class
	int					menu; // What menu we're displaying
	// End modification code
	int					m_iPlayerSound;// the index of the sound list slot reservered for this player
	int					m_iTargetVolume;// ideal sound volume.
Now that should be pretty simple to understand. PS: If you don't save each different working version of your mod in different folders, it is good to have // Start modification code and // End modification code so you can skip to your modification bits. NEVER delete the REAL Half-Life source code. Just comment it with /* and */ so you can replace it with your modification code. You don't have to but I think it's a good idea.
Next up, open multiplay_gamerules.cpp (line 441):

if ( addDefault )
{
	/* Original Half-Life Code
	pPlayer->GiveNamedItem( "weapon_crowbar" );
	pPlayer->GiveNamedItem( "weapon_9mmhandgun" );
	pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );// 4 full reloads
	*/

	// Start modification code
	if (pPlayer->m_iFutureClass < 1 || pPlayer -> m_iFutureClass > 3)	// If they're not a valid class
		pPlayer->m_iFutureClass = 1;					// Set them to the first class

	pPlayer->m_iPlayerClass = pPlayer->m_iFutureClass;		// It's not the future class anymore.  It's the present

	if (pPlayer->m_iPlayerClass == 1)					// If he's class 1 (Marine)
	{//Marine
		g_engfuncs.pfnSetClientMaxspeed( ENT(pPlayer->pev), 400);		// Set his max speed to 400
		pPlayer->pev->health = 125;						// Set health to 125
		pPlayer->pev->armorvalue = 110;						// Set armor to 110
		pPlayer->GiveNamedItem( "weapon_crowbar" );				// Give him a crowbar
		pPlayer->GiveNamedItem( "handgrenade" );				// Give him 2 grenades
		pPlayer->GiveNamedItem( "handgrenade" );
		pPlayer->GiveNamedItem( "weapon_9mmAR" );				// Give him a MP5
		pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );		// Give him ammo for it
		ClientPrint( pPlayer->pev , HUD_PRINTTALK , "You are now a Marine!" );	// Tell him he's a Marine
	}
	else if (pPlayer->m_iPlayerClass == 2)				// If he's class 2 (Messenger)
	{//Messenger
		g_engfuncs.pfnSetClientMaxspeed( ENT(pPlayer->pev), 1200);	// Set his max speed to 1200
		pPlayer->pev->health = 90;							// Set health to 90
		pPlayer->pev->armorvalue = 90;						// Set armor to 90
		pPlayer->GiveNamedItem( "weapon_crowbar" );				// Give him a crowbar
		pPlayer->GiveNamedItem( "weapon_9mmhandgun" );			// Give him the normal hand gun
		pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );		// Give him ammo
		ClientPrint( pPlayer->pev , HUD_PRINTTALK , "You are now a Messenger!" );	// Tell him he's a Messenger
	}
	// End Modification Code
}
That code is now in CHalfLifeMultiplay :: PlayerSpawn(...). Or at least should be heh. If you're wondering, up at the top of the new code for this part it is pPlayer->m_iFutureClass > 3 The 3rd is a simple observer mode à la TFC. What's different between mine and the TFC one is that you "observe" from where you will spawn the first time you join. Let's work on this part now. Open teamplay_gamerules.cpp (line 268):

void CHalfLifeTeamplay::InitHUD( CBasePlayer * pPlayer )
{
	// Start Modification Code
	pPlayer->m_iPlayerClass = 3;			// Sets present class to 3
	pPlayer->m_iFutureClass = 3;			// Sets future class to 3
	ShowMenu(pPlayer,0xFF,0,0,"#selectclass");	// Shows the class menu
	// End Modification Code
	CHalfLifeMultiplay::InitHUD( pPlayer );

	RecountTeams();

Now skip to the end of this function...

			MESSAGE_END();
		}
	}

	// Start Modification Code
	pPlayer->pev->solid = 0;							// I'm invisible
	pPlayer->pev->takedamage = DAMAGE_NO;				// I don't take damage
	pPlayer->EnableControl( false );						// and I can't move!
	// Instead of the above line, put in the following:
	// pPlayer->pev->movetype = MOVETYPE_NOCLIP;
	// to be able to move freely.  TAKE NOTE: When you select a class you will spawn wherever you are!
	ClientPrint( pPlayer->pev, HUD_PRINTTALK, "Welcome to my mod!");	// Prints Welcome to my mod! on player's screen
	// End Modification Code
}
At the beginning... it sets are class and future class to 3 meaning we are observing. If you're wondering how I got the ShowMenu(...) function I'll tell you where and what to write in a jiffy. The code at the bottom makes you invisible, unable to take damage, disables your controls and prints to the player a welcome message. Now here's the ShowMenu(...) function. Go to teamplay_gamerules.cpp (line 31):

extern DLL_GLOBAL_BOOL		g_fGameOver;

// Start Modification Code
extern	int					gmsgShowMenu;

void ShowMenu(CBasePlayer * pPlayer, int bitsValidSlots, int DisplayTime, bool DisplayNow, char menu[500])
{
	MESSAGE_BEGIN( MSG_ONE, gmsgShowMenu, NULL, pPlayer->pev);
		WRITE_SHORT( bitsValidSlots );
		WRITE_CHAR( DisplayTime );
		WRITE_BYTE( DisplayNow );
		WRITE_STRING ( menu );
	MESSAGE_END();
}
// End Modification Code

Let me explain:
pPlayer -> This is which client's screen it will print the menu on
bitsValidSlots -> Uses bits to determine which slots are selectable
	(If you set it to 0xFF ALL slots are available)
DisplayTime -> from 1 to 127 for amount of time the menu shows.
	If it's 0 to -128 it's unlimited.
DisplayNow -> If set to 0, it will display the menu right away.
	If set to 1, it'll know that it needs more information to display it's menu
menu[500] -> Contains the text the menu will display (limit 499 characters on this one)
That should do it.
Now how are you going to get the selected class? I'll show you. teamplay_gamerules.cpp (line 159):

BOOL CHalfLifeTeamplay :: ClientCommand( CBasePlayer * pPlayer, const char *pcmd )
{
	if ( FStrEq( pcmd, "menuselect" ) )
	{
		if ( CMD_ARGV() < 2)
			return TRUE;

		int slot = atoi( CMD_ARGS(1) );

		// Start Modification Code
		// If it's the changeclass menu...
		if (pPlayer->menu == 1)
		{
			if (slot > 0 && slot < 3)			// If it's not a valid class #, ignore it
			{
				pPlayer->m_iFutureClass = slot;
				if (pPlayer->m_iPlayerClass==3)
				{
					pPlayer->pev->solid = 1;					// They're visible
					pPlayer->pev->takedamage = DAMAGE_YES;		// They take damage
					pPlayer->EnableControl( true );					// Controls enabled
					// If in the InitHUD function (you'll see later) you wrote 
					// pPlayer->pev->movetype = MOVETYPE_NOCLIP; instead,
					// Write: pPlayer->pev->movetype = 1; instead of EnableControl( true );

					CHalfLifeMultiplay :: InitHUD (pPlayer);
				}
			}
		}
		else if (pPlayer->menu == 2)
		{
			if (slot > 0 && slot < 3)			// If it's a valid class #, display the info
				ShowClassInfo(pPlayer,slot);
		}
		// End Modification Code

		return TRUE;
	}

	// Start Modification Code
	else if ( FStrEq( pcmd, "changeclass" ) )			// If player typed changeclass on the console
	{
		pPlayer->menu = 1;
		ShowMenu(pPlayer,0xFF,0,0,"#selectclass");	// Show selectclass menu
		return TRUE;						// It worked fine.  Do not return error message
	}
	else if ( FStrEq( pcmd, "playerinfo" ) )
	{
		ShowPlayerInfo(pPlayer);				// Show player's info
		return TRUE;						// It worked fine.  Do not return error message
	}
	else if ( FStrEq( pcmd, "classinfo" ) )
	{
		pPlayer->menu = 2;
		ShowMenu(pPlayer,0xFF,0,0,"#classinfo");		// Show's selectclass menu
		return TRUE;
	}
	// End Modification Code

	return FALSE;
}
OK that should still be pretty easy. If the player wrote menuselect, it'll see if he wrote a number beside it. If so, it puts that number to int slot. Our code checks if the slot is a valid class #. If so, we have a new future class. If the player wrote changeclass in the console, We show the selectclass menu. If he wrote playerinfo, we show the player info. But what is the selectclass menu and what is ShowPlayerInfo(pPlayer)? That's due up next.
This part doesn't go in the DLL. Make a file called Titles.txt and put it in your mod folder. Titles.txt (line 1):

selectclass
{
Select a class:

1. Marine
2. Messenger
}
That's it! So when we show a selectclass menu, it'll write what we just wrote between the { and }. Now for playerinfo. teamplay_gamerules.cpp (line 46 (should be right underneath our ShowMenu command)):

// Start Modification Code
void ShowPlayerInfo(CBasePlayer * pPlayer)
{
	if (pPlayer->m_iPlayerClass == 1)
		ClientPrint(pPlayer->pev, HUD_PRINTTALK, "You are a Marine");
	else if (pPlayer->m_iPlayerClass == 2)
		ClientPrint(pPlayer->pev, HUD_PRINTTALK, "You are a Messenger");
	else if (pPlayer->m_iPlayerClass == 3)
		ClientPrint(pPlayer->pev, HUD_PRINTTALK, "Awaiting decision");

	if (pPlayer->m_iFutureClass == 1)
		ClientPrint(pPlayer->pev, HUD_PRINTTALK, "You will be a Marine");
	else if (pPlayer->m_iFutureClass == 2)
		ClientPrint(pPlayer->pev, HUD_PRINTTALK, "You will be a Messenger");
}
// End Modification Code
This tells what the player is. Marine, Messenger or if he hasn't picked yet it'll tell him so. It'll also tell him what his future class is.
Next up is classinfo.  teamplay_gamerules.cpp (underneath what we just did):

// Start Modification Code
void ShowClassInfo(CBasePlayer * pPlayer, int PlayerClass)
{
	if (PlayerClass == 1)
		ShowMenu(pPlayer,0x0,64,0,"#MarineInfo");
	if (PlayerClass == 2)
		ShowMenu(pPlayer,0x0,64,0,"#MessengerInfo");
}
// End Modification Code
Now we'll need to write the info down for each character. Half-Life/MyMod/Titles.txt (at bottom):

MarineInfo
{
Class: Marine

The Marine is an average type.
He has 1/3 the speed of a Messenger.
He has a crowbar, an mp5 and
2 hand greandes.
Health: 125
Armor: 110
}

MessengerInfo
{
Class: Messenger

The Messenger is a speedy type.
He is very quick.
He has a crowbar and a 9mm hand gun.
Health: 90
Armor: 90
}
Note: By default, Half-Life binds keys 1 through 5 but not 6 through 0. So in Half-Life/MyMod/config.cfg add:

bind "6" "slot6"
bind "7" "slot7"
bind "8" "slot8"
bind "9" "slot9"
bind "0" "slot10"

There you have it!  Now when you're in the game, go to the console and type:

changeclass

to show the menu.  Type:

playerinfo

to show your info.  Type:

classinfo

to get class information.
Here's some additional stuff. Before I start, let me tell you this: I do not have good experience with PAK files... it always messes my files up (almost). But here's what you can do. You may have realized that there is a Controls section in Half-Life (if not, dear god!) and in some mods there are more controls. How do they do that? It's actually quite simple. Get a PAK Editor (I prefer QPed 2) and open up Half-Life/valve/pak0.PAK. Go to the folder gfx/shell and export kb_act.lst and kb_def.lst to Half-Life/MyMod. I'm not quite sure which one is which, but there is one that looks like this:

"UPARROW"		"+forward"
"DOWNARROW"		"+backward"

and there's one that looks like this:

"+forward"			"Move forward"
"+backward			"Move backward"

at the top of the first one, add:

"b"				"changeclass"
"i"				"playerinfo"
"c"				"classinfo"

and in the top of the second one add:

"changeclass"			"Change class"
"playerinfo"			"Show player info"
"classinfo"			"Show class info"

And that's the end of my tutorial!

Any questions or suggestions should be sent to me: rkzad@hotmail.com