|
|||||||
|
|
|
|||||
|
Half-Life Team System With VGUI Menu w/SDK 2.2
Things you will need to complete this tutorial:
SDK 2.2 (Full SDK 2.2 / Standard SDK 2.2 which features source code only)
Visual C++ (I am using version 6.0 havn't tested this with other versions)
Okay first off, this tutorial is based off DarkKnight's tutorial he made this for SDK 2.0, but since so many people out there were getting so many problems with it (including me when i tried out his tut) i thought that i'd make one that works with SDK 2.2 to help everyone out and to put a end to all the forum spam with this tutorial =) DarkKnight absolutely deserves the credit for this tutorial because if in my opinion his tutorial was ground braking, it helped a lot of people because he took the time to actualy make the tutorial and release his source code which a lot of people out there don't want to do, and now to stop my endless spam, let get to the tutorial!
TIPS
ctrl+g opens up a Go To form, which allows you to jump to specific lines, just enter which line you want to jump to and it takes you to that line in the current file.
basic principles: old code in RED new code in BLUE comments in GREEN
1. Observer Setup
First thing we need to do is setup the observer mode. What this will do is, when the player joins the game, he or she will be unable to move, use weapons, etc. They will just stand there until they choose a team. Also they will not be able to be hit. It's as if they are not part of the game. The two files will be spending the majority of time with right now are the "teamplay_gamerules.h" and "teamplay_gamerules.cpp" files in the game dll (mp.dll). But first we have to change the "gamerules.cpp" file to let them know we want to make a teamplay mod. So open up "gamerules.cpp"
Now down at the bottom, look for CGameRules *InstallGameRules( void ) you can find it around line 311. We need to change that function. So completely change it so it reads this:
//Chip - We want a teamplay mod CGameRules *InstallGameRules( void ) { SERVER_COMMAND( "exec game.cfg\n" ); SERVER_EXECUTE( ); if ( !gpGlobals->deathmatch ) { //No deathmatch defined but we want it anyways. gpGlobals->deathmatch = TRUE; return new CHalfLifeTeamplay; } else { //Deathmatch, so let's start Teamplay return new CHalfLifeTeamplay; } } //Chip
/*
//=========================================================
// instantiate the proper game rules object
//=========================================================
CGameRules *InstallGameRules( void )
{
SERVER_COMMAND( "exec game.cfg\n" );
SERVER_EXECUTE( );
if ( !gpGlobals->deathmatch )
{
// generic half-life
g_teamplay = 0;
return new CHalfLifeRules;
}
else
{
if ( teamplay.value > 0 )
{
// teamplay
g_teamplay = 1;
return new CHalfLifeTeamplay;
}
if ((int)gpGlobals->deathmatch == 1)
{
// vanilla deathmatch
g_teamplay = 0;
return new CHalfLifeMultiplay;
}
else
{
// vanilla deathmatch??
g_teamplay = 0;
return new CHalfLifeMultiplay;
}
}
*/
you dont have to copy/paste the commented file that i put here, i inserted it here just for reffrence so that everyone knows what function DarkKnight is referring to, though i do recommend keeping the original source code commented out so that if you ever want to go back to old things its there, or if you majorly screw up and want to revert back.
Ok now the engine knows that this will be a Teamplay mod. This function simply tells the game that if the gameplay is going to be deathmatch, then we want it to be teamplay. Now we need to fix something that Valve put in there. In "teamplay_gamerules.cpp", we need to fix a line of code that will automatically assign the player to a team. So find CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) (around line 211) and you'll see this:
void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) { int i; SetDefaultPlayerTeam( pPlayer ); CHalfLifeMultiplay::InitHUD( pPlayer ); // Send down the team names MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() ); WRITE_BYTE( num_teams ); for ( i = 0; i < num_teams; i++ ) { WRITE_STRING( team_names[ i ] ); } MESSAGE_END(); RecountTeams();
Simply delete or comment out SetDefaultPlayerTeam( *pPlayer ) and this problem will be resolved. Ok now we are going to set up the team names. To do so, open up "teamplay_gamerules.h" and look at the top for this code:
#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH
and right under it add:
//Chip - Lets add the team names //replace "Team Name Here" with your team names but keep the quotation marks #define TEAM1_NAME "Team Name Here" #define TEAM2_NAME "Team Name Here" //Chip
Change "Team Name Here" to what you want your teams name to be, for instance you can have TEAM1_NAME "Jocks" and TEAM2_NAME "Geeks" and that's basically it =P
Now we need to spawn the observer, the way they should be spawned. Unable to move, fire weapons, etc. Also this prevents him or her from looking at the forces one team has and then joining the other team. First in "multiplay_gamerules.cpp" look at like 25, you will see this:
#include "skill.h"
#include "game.h"
#include "items.h"
#include "voice_gamemgr.h"
Right after "#include "Voice_gamemgr.h" we need to add:
#include "teamplay_gamerules.h"
Now to set up the spawn function. Go to void CHalfLifeMultiplay :: PlayerSpawn( CBasePlayer *pPlayer ) (around like 556) after the code:
addDefault = TRUE; while ( pWeaponEntity = UTIL_FindEntityByClassname( pWeaponEntity, "game_player_equip" )) { pWeaponEntity->Touch( pPlayer ); addDefault = FALSE; }
add:
//Chip - Observer Spawn. Disables Movement and the such if ((!FStrEq(pPlayer->m_szTeamName, TEAM1_NAME)) && (!FStrEq(pPlayer->m_szTeamName, TEAM2_NAME))) { // Make player dead to the game pPlayer->pev->classname = MAKE_STRING("observer"); pPlayer->pev->solid = SOLID_NOT; pPlayer->pev->takedamage = DAMAGE_NO; pPlayer->pev->movetype = MOVETYPE_NONE; pPlayer->pev->flags |= FL_NOTARGET; pPlayer->pev->effects |= EF_NODRAW; pPlayer->m_afPhysicsFlags |= PFLAG_OBSERVER; pPlayer->m_iHideHUD |= HIDEHUD_WEAPONS | HIDEHUD_FLASHLIGHT | HIDEHUD_HEALTH; addDefault = FALSE; } else { // Make sure the player can see the HUD again pPlayer->m_iHideHUD &= ~HIDEHUD_WEAPONS; pPlayer->m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; pPlayer->m_iHideHUD &= ~HIDEHUD_HEALTH; pPlayer->m_afPhysicsFlags &= ~PFLAG_OBSERVER; } //Chip
Alright so now the player just sits there, dead to the game. Now we need to set it up where they can't move. Open "player.cpp" and look for void CBasePlayer::PreThink(void) (around line 1651) and and find the following code:
// JOHN: checks if new client data (for HUD and view
control) needs to be sent to the client
UpdateClientData();
CheckTimeBasedDamage();
CheckSuitUpdate();
if (pev->deadflag >= DEAD_DYING)
{
PlayerDeathThink();
return;
}
// So the correct flags get sent to client asap.
//
Now right under that add the following code:
//Chip - Make observer
unable to move
if (
m_afPhysicsFlags & PFLAG_ONTRAIN )
//Chip
now while in the same function find:
// Clear out ladder pointer
m_hEnemy = NULL;
you can find it around line 1770 and right after it add:
if ( m_afPhysicsFlags & PFLAG_ONTRAIN )
right under that you should see this:
{
pev->velocity = g_vecZero;
}
after that add this:
//Chip - Enable Control being False means player has no
control of player in game
if ( m_afPhysicsFlags &
PFLAG_OBSERVER )
{
EnableControl(FALSE);
}
else
EnableControl(TRUE);
//Chip
Now go to line 1670 or around it, you should see this:
g_pGameRules->PlayerThink( this ); if ( g_fGameOver ) return; // intermission or finale UTIL_MakeVectors(pev->v_angle); // is this still used? ItemPreFrame( ); WaterMove(); if ( g_pGameRules && g_pGameRules->FAllowFlashlight() )
right after that add:
//Chip - Disable the Flashlight for the observer
if ( m_afPhysicsFlags &
PFLAG_OBSERVER )
m_iHideHUD |=
HIDEHUD_FLASHLIGHT;
else
m_iHideHUD &=
~HIDEHUD_FLASHLIGHT;
//RR2DO2
right under that you should see this:
m_iHideHUD &= ~HIDEHUD_FLASHLIGHT;
delete or comment that out.
What that last bit of code did was make sure the flashlight was disabled on the HUD. Now we need to fix it so our observer can't use cheats and get all the weapons before they choose a team (cheats should always be disabled anyway :)). In "player.cpp" still, look for CBasePlayer::GiveNamedItem around line 3138, find the following line of code.
ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" ); return; }
right after that add this::
//Chip - Observers can't use cheats if ( m_afPhysicsFlags & PFLAG_OBSERVER) { return; } //Chip
What that last bit of code did was when a player types in the "give XXX" command, it will return and simply not give them anything at all. There is still some flashlight code that we need to change so still in "player.cpp" look for void CBasePlayer :: FlashlightTurnOn( void ) around line 3188 now right under it is this code:
if ( !g_pGameRules->FAllowFlashlight() ){ return; }
add the following:
//Chip - Disable Observer Flashlight
if ( m_afPhysicsFlags & PFLAG_OBSERVER)
{
return;
}
//Chip
That code works like the one for GiveNamedItem. When the observer hits their flashlight button, it will simply do nothing. Now we got two more things to deal with on the observer, Impulse Commands and Suicide. Lets take care of Impulse Commands first. Once again in "player.cpp" look for void CBasePlayer::ImpulseCommands( ) around line 3259 and right after:
TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs
add this code:
//Chip - An observer can't send impulse commands if ( m_afPhysicsFlags & PFLAG_OBSERVER) { pev->impulse = 0; return; } //Chip
and finally we take care of the Suicide problem. In "client.cpp" (finally done with player.cpp ;)) look for ClientKill( edict_t *pEntity ) around line 159 and after
entvars_t *pev = &pEntity->v; CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev );
add this code:
//Chip - Disables observer suicide if ( pl->m_afPhysicsFlags & PFLAG_OBSERVER) { return; } //Chip
Finally we are done with the observer. Right now he can't talk, get weapons, cheat, kill himself, scratch his butt, do anything but talk. We'll leave that feature enabled so maybe the observer can see which team may need a hand. Compile this and you should be able to enter the game, but just stand there. Don't do much of anything, but oh well :). Let's move on to the teams.
Now time to get to the nitty gritty stuff of the tutorial. First we need to fix another part of the Valve code. Currently a player can switch teams if he or she changes the model. So lets fix that. The function we will be looking at is the CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) around line 313 in "teamplay_gamerules.cpp."
When you get to it you will see this:
//=========================================================
// ClientUserInfoChanged
//=========================================================
void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer
*pPlayer, char *infobuffer )
{
char text[1024];
// prevent skin/color/model changes
char *mdls = g_engfuncs.pfnInfoKeyValue( infobuffer,
"model" );
if ( !stricmp( mdls, pPlayer->m_szTeamName ) )
return;
if ( defaultteam.value )
{
int clientIndex = pPlayer->entindex();
g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer(
pPlayer->edict() ), "model", pPlayer->m_szTeamName );
g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer(
pPlayer->edict() ), "team", pPlayer->m_szTeamName );
sprintf( text, "* Not allowed to change teams in this game!\n" );
UTIL_SayText( text, pPlayer );
return;
}
if ( defaultteam.value || !IsValidTeam( mdls ) )
{
int clientIndex = pPlayer->entindex();
g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer(
pPlayer->edict() ), "model", pPlayer->m_szTeamName
);
sprintf( text, "* Can't change team to \'%s\'\n", mdls );
UTIL_SayText( text, pPlayer );
sprintf( text, "* Server limits teams to \'%s\'\n", m_szTeamList );
UTIL_SayText( text, pPlayer );
return;
}
// notify everyone of the team change
sprintf( text, "* %s has changed to team \'%s\'\n", STRING(pPlayer->pev->netname),
mdls );
UTIL_SayTextAll( text, pPlayer );
UTIL_LogPrintf( "\"%s<%i><%u><%s>\" joined team \"%s\"\n",
STRING(pPlayer->pev->netname),
GETPLAYERUSERID( pPlayer->edict() ),
GETPLAYERWONID( pPlayer->edict() ),
pPlayer->m_szTeamName,
mdls );
ChangePlayerTeam( pPlayer, mdls, TRUE, TRUE );
// recound stuff
RecountTeams( TRUE );
}
Now replace the whole ClientUserInfoChanged function (all of the above code) with the following:
//========================================================= // ClientUserInfoChanged //========================================================= void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) { // an observer can change a lot, but isn't visible so hasn't got to do much here if ( pPlayer->m_afPhysicsFlags & PFLAG_OBSERVER) return; // prevent skin/color/model changes char *mdls = g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ); //if the model name and the teamname are the same then return if ( !stricmp( mdls, pPlayer->m_szTeamName ) ) return; //else make sure the player model is the same as the teamname g_engfuncs.pfnSetClientKeyValue( pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); }
With the comments there, you should be able to get a good understanding of what the code is doing. First we prevent model changes, then we check to see if they are currently on a team with the same name as their model, if not we put them there.
Next comes the big function. The JoinTeam function. This will go at the complete bottom of the "teamplay_gamerules.cpp" file.
//=========================================================
// Chip - JoinTeam Function
//=========================================================
void CHalfLifeTeamplay::JoinTeam(CBasePlayer *pPlayer, const char *pTeamName)
{
char text [1024];
//If already on this team, do nothing
if (FStrEq(pPlayer->m_szTeamName, pTeamName))
{
return;
}
//Join the team
//When observer, make it respawnable, let it join a team and
then respawn
if ( pPlayer->m_afPhysicsFlags & PFLAG_OBSERVER)
{
// notify everyone of the team change
sprintf( text, "* %s has joined
%s\n", STRING(pPlayer->pev->netname), pTeamName );
UTIL_SayText( text, pPlayer );
UTIL_LogPrintf( "\"%s<%i>\" has
joined %s\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict()
), pTeamName );
pPlayer->pev->deadflag =
DEAD_RESPAWNABLE;
ChangePlayerTeam( pPlayer, pTeamName,
FALSE, FALSE );
// recound stuff
RecountTeams();
pPlayer->Spawn();
}
//Else just switch teams
else
{
// notify everyone of the team change
sprintf( text, "* %s has joined
%s\n", STRING(pPlayer->pev->netname), pTeamName );
UTIL_SayText( text, pPlayer );
UTIL_LogPrintf( "\"%s<%i>\" has
joined %s\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict()
), pTeamName );
ChangePlayerTeam( pPlayer, pTeamName,
TRUE, TRUE );
// recound stuff
RecountTeams();
}
}
//Chip
This function will put the player on the team they choose from the VGUI that we will add in a few lessons. For right now it won't do much until we get that menu, but it should still compile.
Now open up "teamplay_gamerules.h" now right under:
virtual void ChangePlayerTeam( CBasePlayer *pPlayer,
const char *pTeamName, BOOL bKill, BOOL bGib );
add:
virtual void JoinTeam( CBasePlayer
*pPlayer, const char *pTeamName );
As of right now, when the player joins the game, his team is equal to his model, but that's not what we want. To change this, still in "teamplay_gamerules.cpp" look for CHalfLifeTeamplay::SetDefaultPlayerTeam around line 180 you will see this:
const char *CHalfLifeTeamplay::SetDefaultPlayerTeam(
CBasePlayer *pPlayer )
{
// copy out the team name from the model
char *mdls = g_engfuncs.pfnInfoKeyValue(
g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" );
strncpy( pPlayer->m_szTeamName, mdls, TEAM_NAME_LENGTH );
RecountTeams();
// update the current player of the team he is
joining
if ( pPlayer->m_szTeamName[0] == '\0' || !IsValidTeam( pPlayer->m_szTeamName )
|| defaultteam.value )
{
const char *pTeamName = NULL;
if ( defaultteam.value )
{
pTeamName = team_names[0];
}
else
{
pTeamName = TeamWithFewestPlayers();
}
strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH );
}
return pPlayer->m_szTeamName;
}
Now change that whole function to look like this:
const char *CHalfLifeTeamplay::SetDefaultPlayerTeam(
CBasePlayer *pPlayer )
{
return NULL;
}
Now look at the InitHud() around line 211
//=========================================================
// InitHUD
//=========================================================
void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer )
{
int i;
SetDefaultPlayerTeam( pPlayer );
CHalfLifeMultiplay::InitHUD( pPlayer );
// Send down the team names
MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() );
WRITE_BYTE( num_teams );
for ( i = 0; i < num_teams; i++ )
{
WRITE_STRING( team_names[ i ] );
}
MESSAGE_END();
RecountTeams();
char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict()
), "model" );
// update the current player of the
team he is joining
char text[1024];
if ( !strcmp( mdls, pPlayer->m_szTeamName ) )
{
sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName );
}
else
{
sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName );
}
ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE );
UTIL_SayText( text, pPlayer );
int clientIndex = pPlayer->entindex();
RecountTeams();
// update this player with all the
other players team info
// loop through all active players and send their team info to the new client
for ( i = 1; i <= gpGlobals->maxClients;
i++ )
{
CBaseEntity *plr = UTIL_PlayerByIndex( i );
if ( plr && IsValidTeam( plr->TeamID() ) )
{
MESSAGE_BEGIN( MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() );
WRITE_BYTE( plr->entindex() );
WRITE_STRING( plr->TeamID() );
MESSAGE_END();
}
}
}
Now change it to look like this:
//=========================================================
// InitHUD
//=========================================================
void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer )
{
SetDefaultPlayerTeam( pPlayer );
CHalfLifeMultiplay::InitHUD( pPlayer );
RecountTeams();
char *mdls = g_engfuncs.pfnInfoKeyValue(
g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" );
//
update this player with all the other players team info
// loop through all active players and send their team info
to the new client
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBaseEntity *plr = UTIL_PlayerByIndex(
i );
if ( plr && IsValidTeam( plr->TeamID()
) )
{
MESSAGE_BEGIN(
MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() );
WRITE_BYTE( plr->entindex() );
WRITE_STRING( plr->TeamID() );
MESSAGE_END();
}
}
}
Now we need to fix the CHalfLifeTeamplay::TeamWithFewestPlayers around line 520 right now it just looks for existing teams, but we want it to look for both teams, regardless if they exist (if a team didn't exist then wouldn't it be the team with fewest players :)). Now right after:
int i;
int minPlayers =
MAX_TEAMS;
int teamCount[ MAX_TEAMS
];
char *pTeamName = NULL;
Add the following code:
//check if the hmctf teams exist. If a team doesn't exist it is the team with the fewest players if (GetTeamIndex(TEAM1_NAME) == -1) return TEAM1_NAME; else if (GetTeamIndex(TEAM2_NAME) == -1) return TEAM2_NAME;
That is all for teams for right now. Not to bad now was it :). We will be returning to some of this later on when we began the mapping part. Lets get on to the VGUI stuff.
Ah finally, the moment most of you have been waiting for. This will tell you how to set up a VGUI menu that will allow your players to choose their team. We'll first start out about the VGUI and what it is. VGUI stands for Valve Graphical User Interface. GUI's exist in many other forms, well even your Windows Desktop is a GUI. The VGUI replace the old standard text menus that existed before SDK 2.0 and TFC 1.5. The VGUI also handles displaying the Class menus (which I won't go over sorry) MOTD, map description, anything you really want it to. We will be using Zyborg's tutorial as our framework, followed by some help from Corvidae and day. Shall we begin? We shall :). Ok first thing we need to do is register the message we are going to send in the "player.cpp" file. At the top of the file you should see a bunch of lines that start like "gmsgSomething = 0." Once you see them, right under the last one add which if you haven't changed or added any should be int gmsgTeamNames = 0; around line 184
int gmsgVGUIMenu = 0;
Now a little on down you should see some lines that say like gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 ); and right after that one, add:
gmsgVGUIMenu = REG_USER_MSG("VGUIMenu", 1); // VGUI Message
Now our messages to the client dll have been declared. So now we need to make a new function in "player.cpp." So at the end of the "player.cpp" file add this code:
void ShowVGUI( CBasePlayer *pPlayer, int iMenu ) { MESSAGE_BEGIN( MSG_ONE, gmsgVGUIMenu, NULL, pPlayer->pev ); WRITE_BYTE( iMenu ); // This is the menu number that needs to be sent MESSAGE_END(); }
Ok now our menu that we want to display, needs to correspond with the menu number in the client dll. To do this, Zyborg copied the code from "tf_defs.h" in the client dll and we have this that we need to add to "player.h" at the end.
// ZybOrg - For VGui menues void ShowVGUI( CBasePlayer *pPlayer, int iMenu ); // Add this here too #define MENU_DEFAULT 1 #define MENU_TEAM 2 #define MENU_CLASS 3 #define MENU_MAPBRIEFING 4 #define MENU_INTRO 5 #define MENU_CLASSHELP 6 #define MENU_CLASSHELP2 7 #define MENU_REPEATHELP 8 // ZybOrg - End
Now we are going to set up a command we can bind a key to for easy access. To do this, we need to add another client command to the "client.cpp" file. In ClientCommand() around line 356 look for the following code:
else if ( FStrEq(pcmd, "fov" ) )
{
if ( g_flWeaponCheat && CMD_ARGC() > 1)
{
GetClassPtr((CBasePlayer *)pev)->m_iFOV = atoi( CMD_ARGV(1) );
}
else
{
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"fov\" is \"%d\"\n", (int)GetClassPtr((CBasePlayer
*)pev)->m_iFOV ) );
}
}
else if ( FStrEq(pcmd, "use" ) )
{
GetClassPtr((CBasePlayer *)pev)->SelectItem((char *)CMD_ARGV(1));
}
else if (((pstr = strstr(pcmd, "weapon_")) != NULL) && (pstr == pcmd))
{
GetClassPtr((CBasePlayer *)pev)->SelectItem(pcmd);
}
else if (FStrEq(pcmd, "lastinv" ))
{
GetClassPtr((CBasePlayer *)pev)->SelectLastItem();
}
Now right after that add:
else if (FStrEq(pcmd, "chooseteam" )) { ShowVGUI( GetClassPtr((CBasePlayer *)pev), MENU_TEAM ); // MENU_TEAM is same on client dll }
Right now, the TFC teams would show up in this menu, we don't want TFC teams, unless of course your teams are Red and Blue, which I somewhat doubt. To get our names to show up, we need to pass another message to the client dll containing our team names. So up top of the "player.cpp" file where we began registering our VGUI message, add this line of code:
int gmsgTeams = 0;
and then a little on down again, add this:
gmsgTeams = REG_USER_MSG("TeamNames", -1);
Now our team message is registered (thanks to Corvidae for this help), we need to send the actual message. For this, we go to "multiplay_gamerules.cpp" and the InitHUD function around line 413. Look for the following code:
if ( g_fGameOver )
{
MESSAGE_BEGIN( MSG_ONE, SVC_INTERMISSION, NULL, pl->edict() );
MESSAGE_END();
}
right after it add this:
MESSAGE_BEGIN(MSG_ONE, gmsgTeams, NULL, pl->edict()); WRITE_BYTE(2); // Two teams. (You can have up to 4.) WRITE_STRING(TEAM1_NAME); // You need to pass the teams as literal strings WRITE_STRING(TEAM2_NAME); MESSAGE_END();
Now we want our players to choose their teams the second they connect to the game, so what we need to do is have the menu appear to them when they spawn. So to this, we need to modify the "mulitplay_gamerules.cpp" file, more specifically the InitHUD function once again. So, first right after
void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) {
add:
extern int gmsgTeams;
Then after our message containing the team names, add this line of code:
ShowVGUI (pl, MENU_TEAM);
Now, if you compile this, your menu should show up, but you wont be able to join a team. To do this, we need to add another client command to the "teamplay_gamerules.cpp" to the ClientCommand(). which is around line 146, find this code:
if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd))
return TRUE;
add this:
else if ( FStrEq( pcmd, "jointeam" ) ) { int slot = atoi( CMD_ARGV(1) );//we need to re-identify the slot switch(slot) { case 1: JoinTeam(pPlayer, TEAM1_NAME); break;//slot 1 case 2: JoinTeam(pPlayer, TEAM2_NAME); break;//slot 2 case 5: //slot 5 switch(RANDOM_LONG(1,2)) { case 1: JoinTeam(pPlayer, TEAM1_NAME); break; case 2: JoinTeam(pPlayer, TEAM2_NAME); break; } } }
Now you will have a working a VGUI that will put you on a team. Currently, the Auto Team button, will put you on a pure random team. You could change that to the team with the fewest players if you wanted to (which is what I did, hey it's not hard, you should be able to figure it out ;)). So go and enjoy the working VGUI and then come back and we'll tweak it up some :).
NOTE
if you dont add this code to the right part of ClientCommand() you will get error when you choose teams (unknown functions "jointeam") so make sure you place it where i told you to =]
Ok if you noticed on your menu, it looked kinda of messed up. No title, the Auto Team button said something like #5_TEAM_AUTO, well lets fix that shall we.
The only file we will be coding, is the "VGUI_teammenu.cpp" file. It's pretty straight forward and well commented, so see how Valve did something shouldn't be to difficult. Go to line 72 and you should see:
pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourTeam"));
Change "#Title_SelectYourTeam" to whatever you want it to be. You dont need to have the # but you do need the quotation marks since you are passing a string. Now go to line 122 and you will see:
m_pButtons[5]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Team_AutoAssign") );
Just change "#Team_AutoAssign" to something like "Auto Team" or "Random Team" whatever you would like. If you look on down through the file, you will see several instances of "#SOMETHING" and you can just change them to whatever you need them to say. That about wraps up the tweaking part. Oh yeah to get a map description to show up, just make a text file in your map directory that has the same name as your map and the description will show up in the VGUI.
Another way to do this, which Corvidae brought to my attention, is to use the
global string text file. Simply copy the "titles.txt" file from the \tfc
directory (the one in \valve doesn't contain the entries you want to use) to
your mod's root directory. This will change the "#
Team_AutoAssign { AUTO ASSIGN }
All you need to do is to change the text inside the braces to whatever you'd
like. The next time you start the server, your new text should appear. In case
you're curious, you can use the titles.txt file to replace ANY strings in your
code if you wish. Simply add a new entry like the one above to the end of the
file, edit it to what you want, and add a "#
Now for a small fix with team names, valve's basic teamplay code is really simple, so a simple fix with valves code that we need to make is on the way! We need to fix the way team names show up to clients, so open up "vgui_ScorePanel.cpp" and look for:
else
{
sprintf( sz2, gViewPort->GetTeamName(team_info->teamnumber) );
}
just change that to:
else
{
sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( team_info->name ) );
}
Open up the game dll (mp.dll) and go to players.cpp and look for "int gmsgTeamNames = 0;" which will be around line 184, now either delete it out comment it out, next thing is to look for "gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 );" and either delete or comment that out as well, and 1 last and final thing is to open up "multiplay_gamerules.cpp" and go to InitHud() which is around line 414 and look for:
// Send down the team namesMESSAGE_BEGIN( MSG_ONE, gmsgTeamNames,
NULL, pPlayer->edict() );
WRITE_BYTE( num_teams );
for ( i = 0; i < num_teams; i++ )
{
WRITE_STRING( team_names[ i ] );
}
MESSAGE_END();
and like the other ones, either delete or comment it out.
If you've completed the tutorial and when ever you
start up your game you get an error that crashes hl then
you missplaced the jointeam command, the common issue people make is
because there is a "if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd))"
in multiplay_gamerules.cpp so they place the command there, instead of
teamplay_gamerules.cpp like directed to.
Another common issue is getting client 100 error no client present, i
havnt seen what's causing this error but i belive that its caused by a
similar error
by people misplacing the code into a different file than supposed to, if
anyone finds out what mistake they made to get this error, please
notify me and tell me what you did wrong
and how you fixed it.
Like i stated before, DarkKnight deserves all the credit for this, all i did is make it compatible with SDK 2.2, but if it wasn't for DarkKnights tutorial i doubt i would have ever done this, you can e-mail him at chip@lambertsoft.com and thank him for taking the time to write a tutorial so good and in such high demand! well that about sums it up, if you guys want to drop me a line my e-mail is Senjuro83@hotmail.com and my AIM sn is ShinjiRE, i am also working on my own mod that's just for fun! its called Enraged-Life, check it out at http://elife.5u.com
![]() |