Well I'm moving this tutorial over from Half-Life Coding Index to Wavelength since HLCI is hosted at Wavelength anyways, and HLCI is an index, not a tutorial. Feel free to PM/E-Mail me about any mistakes, but if you have any problems with VGUI please ask your questions on the Half-Life coding forum. I haven't touched Half-Life in about a year aside from updating my website, so I'm sure there are more qualified people who are willing to help on the forum.
Now, let me tell you exactly what we're gonna be doing today. First, we will create a very basic menu. Only a cancel button. Later on, we will add more buttons sending commands and doing all sorts of nifty things. So, because we are working with some VGUI, if you want to be able to actually try this out in your own code, make sure you have a compiler that will be able to code both the mp/hl.dll and the client.dll. One compiler that I am certain can do both, and is most recommended, is Microsoft Visual C++ 6 (MSVC). Next, make sure that you're liblist.gam has the following inside:
These are just some necessary things to make sure your mod will look for a client.dll also. It will search for this dll in "your_mod_folder/cl_dlls/client.dll", while it will look for your mp/hl.dll wherever you specified it in the "gamedll" parameter. Enough about liblist.gam though (there are some tutorials on this you can find elsewhere). Let's get coding!
Let's open up the mp/hl workspace, and open player.h. Insert this at line 74:
| | class CBasePlayer : public CBaseMonster { public: void ShowVGUIMenu(int iMenuID); int random_seed; |
There we go. This is a prototype for our function that will tell the client dll to show one of the VGUI menus.
Now, let's implement it. Open player.cpp and go to line 237:
| | LINK_ENTITY_TO_CLASS( player, CBasePlayer );
void CBasePlayer :: ShowVGUIMenu(int iMenuID) { MESSAGE_BEGIN(MSG_ONE, gmsgVGUIMenu, NULL, pev); WRITE_BYTE( iMenuID ); MESSAGE_END(); }
void CBasePlayer :: Pain( void ) |
But, hold on a sec. Where did gmsgVGUIMenu come from? That's right! It's not declared. First let's go to line 190:
| | int gmsgShowMenu = 0; int gmsgGeigerRange = 0; int gmsgVGUIMenu = 0;
void LinkUserMessages( void ) |
Next, let's go to the end of that LinkUserMessages and add the following:
| | gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); gmsgAmmoX = REG_USER_MSG("AmmoX", 2); gmsgVGUIMenu = REG_USER_MSG("VGUIMenu", 1); }
LINK_ENTITY_TO_CLASS( player, CBasePlayer ); |
The above is needed to tell the client dll what message we are sending in ShowVGUIMenu. Now that that's done, it's time to put our ShowVGUIMenu to use! Let's open client.cpp and go to line 380:
| | GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); } else if ( FStrEq(pcmd, "vguimenu" ) ) { if (CMD_ARGC() >= 1) GetClassPtr((CBasePlayer *)pev)->ShowVGUIMenu(atoi(CMD_ARGV(1))); } else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) |
Now, we can use the command "vguimenu xx" to access any of the VGUI Menus we want! Build your mp.dll and put it in your mod's gamedll folder. Start a game of HL, and in the console type "vguimenu 5" (without the ") and something close to the following should show:
So that menu may not look the best... but that's besides the point. That was a test to see whether or not our new vguimenu command works. Now, we will finally create our own new VGUI menu! Lets open up the cl_dll project now. Open up vgui_TeamFortressViewport.h at line 46:
| | class CClassMenuPanel; class CTeamMenuPanel; class CFirstMenu;
char* GetVGUITGAName(const char *pszName); |
Now we have our class prototyped, just in case we need to reference to it before we wrote the next bit of text (goto line 1118):
| | public: CTFScrollPanel(int x,int y,int wide,int tall); };
class CFirstMenu : public CMenuPanel { private: CommandButton *m_pCancelButton;
public: CFirstMenu(int iTrans, int iRemoveMe, int x, int y, int wide, int tall); };
|
There we go, we have our first menu class. Now let's implement our constructor. Add a new file to the Source Files of the cl_dll project named "vgui_FirstMenu.cpp". Open up that file and write in the following:
| | #include "hud.h" #include "cl_util.h" #include "vgui_TeamFortressViewport.h"
CFirstMenu :: CFirstMenu(int iTrans, int iRemoveMe, int x, int y, int wide, int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) { m_pCancelButton = new CommandButton( gHUD.m_TextMessage.BufferedLocaliseTextString( "Cancel" ), 5, 5, XRES(75), YRES(30)); m_pCancelButton->setParent( this ); m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) ); } |
Now, what this does, is create our cancel button with "Cancel" as the text on it, position it at x(5), y(5), and let it have a width of 75 and a height of 30. XRES and YRES make our numbers relative to either 640x480 or 320x240. The second line tells CFirstMenu that it now has a new child that it has to show, or hide, or do whatever it should to it's children. The last line makes it so that when you click on the button, it'll send a MenuHandler that will hide our menu. Next, we will make the necessary changes so that we may open our VGUI menu through our ShowVGUIMenu command. Goto line 278:
| | void CreateClassMenu( void ); CMenuPanel* ShowClassMenu( void ); void CreateFirstMenu( void ); CMenuPanel* ShowFirstMenu( void ); void CreateSpectatorMenu( void ); |
The first function, later on, is what gets called early in the VGUI starting stage to create our menu. The second function gets called every time we want our FirstMenu to show up. Next, go to line 399 and add the following:
| | CTeamMenuPanel *m_pTeamMenu; CClassMenuPanel *m_pClassMenu; CFirstMenu *m_pFirstMenu; ScorePanel *m_pScoreBoard; |
Now, our FirstMenu is part of the actual TeamFortressViewport class.
That's it for vgui_TeamFortressViewport.h! Now, open vgui_TeamFortressViewport.cpp! Let's take a look at line 468:
| | m_pTeamMenu = NULL; m_pClassMenu = NULL; m_pFirstMenu = NULL; m_pScoreBoard = NULL; |
These are necessary so that we don't get some wacko information in our menu. So we make it all NULL. Later on in this function we will create our menu. You can do that at line 532:
| | CreateTeamMenu(); CreateClassMenu(); CreateFirstMenu(); CreateScoreBoard(); |
Now, when the TeamFortressViewport is being constructed, our FirstMenu will be created! Let's continue on to line 553:
| | if (m_pTeamMenu) { m_pTeamMenu->Initialize(); } if (m_pClassMenu) { m_pClassMenu->Initialize(); } if (m_pFirstMenu) { m_pFirstMenu->setVisible( false ); } if (m_pScoreBoard) { m_pScoreBoard->Initialize(); HideScoreBoard(); } |
This is necessary so that whenver a new map gets loaded, our menu will be hidden again. Now, let's make a quick little change in tf_defs.h (External Dependencies) at line 1132:
| | #define MENU_REFRESH_RATE 25
#define MENU_FIRSTMENU 30
|
Now, we have a defined number assigned to our FirstMenu! We may use this number when we use vguimenu (ex: "vguimenu 30").
Now, back to vgui_TeamFortressViewport.cpp, let's go all the way down to line 1506:
| | case MENU_CLASS: pNewMenu = ShowClassMenu(); break;
case MENU_FIRSTMENU: pNewMenu = ShowFirstMenu(); break;
default: break; } |
Does the name of the function we're putting this in look familiar? Well it should if you were paying attention to all I've been saying for a long while now. No, this is not the same thing we had before. This is what the actual VGUI code is using to show our menus. See how it'll take the MenuID (in our case, 30), and then when it gets to case MENU_FIRSTMENU, since MenuID equals 30, it will show our FirstMenu, cool, huh?
Now then. We've made our ShowFirstMenu and CreateFirstMenu prototypes, and we've used the functions on several occasions in the code. But we still haven't implemented them! Let's go down to line 1618 and then add the following:
| | m_pClassMenu->setParent(this); m_pClassMenu->setVisible( false ); }
CMenuPanel* TeamFortressViewport::ShowFirstMenu() { if ( gEngfuncs.pDemoAPI->IsPlayingback() ) return NULL;
m_pFirstMenu->Reset(); return m_pFirstMenu; }
void TeamFortressViewport::CreateFirstMenu() { m_pFirstMenu = new CFirstMenu(100, false, 0, 0, ScreenWidth, ScreenHeight); m_pFirstMenu->setParent(this); m_pFirstMenu->setVisible( false ); }
|
There we go! Now just compile your client.dll and move it to your mod's cl_dlls folder. Run Half-Life with your mod as your custom game, and start a game. In the console, type vguimenu 30 and, low and behold, the following pops up:
Yeah, it's not much of a menu. But this tutorial has shown you how you can create your own. This is just the basic start! Check out part 2 for more fun with our menu. |