Wavelength Logo
tl.jpg (2351 bytes) blank.gif (837 bytes) tr.jpg (2446 bytes)
blank.gif (837 bytes)
Menus in Half-Life by Tim Smith
blank.gif (837 bytes)
 

Basics
The client.dll that comes with Half-Life contains basic support for a menuing system that should suffice for most people.  Menus are displayed and the results sent back to the user in a series of basic steps.

  1. The server sends to the request to the client telling him to display a menu
  2. The user selects an option
  3. Client sends a 'menuselect #' command to the server

 

Displaying Menus
All menu operations are performed using a single client message called ShowMenu.   This message has three parameters.  Instead of generating the message by hand every time, it is best to create a routine to display the menu.

extern int gmsgShowMenu; // the message # for ShowMenu

void ShowMenu (CBasePlayer *pPlayer, int bitsValidSlots, int nDisplayTime, BOOL fNeedMore, char *pszText)
{
    MESSAGE_BEGIN( MSG_ONE, gmsgShowMenu, NULL, pPlayer ->pev);
        WRITE_SHORT( bitsValidSlots);
        WRITE_CHAR( nDisplayTime );
        WRITE_BYTE( fNeedMore );
        WRITE_STRING (pszText);
    MESSAGE_END();
}

pPlayer is the client that will display the menu.

bitsValidSlots is a bitmask that informs the menuing system which options in the menu are valid.  Bit 0 is option #1, Bit 1 is option #2, ..., Bit 9 is option #0.

nDisplayTime is the number of seconds that the menu will be displayed.  The maximum time is 127 seconds.  If the value is between 0 and -128 inclusive, then the menu will not time out.

fNeedMore informs the client that there is more menu information to be sent.  If fNeedmore is FALSE, then the menu will be displayed.  If it is TRUE, then the menu system will wait for another ShowMenu message.  The text of the multiple ShowMenu messages will be concatenated.   When the client receives a ShowMenu message with fNeedMore set to FALSE, then and only then will it display the menu.  The values of bitsValidSlots and nDisplayTime will be ignored for all ShowMenu messages where fNeedMore is TRUE.

 

Handling the MENUSELECT Command From the Client
In the SDK, the CHalfLifeTeamplay subclass of the CGameRules class contains a method called ClientCommand.  This method has a sample of handling the MENUSELECT command from the client.

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

        int slot = atoi( CMD_ARGV(1) );

        // select the item from the current menu

        return TRUE;
    }

    return FALSE;
}

When the MENUSELECT command is received, the code verifies that the slot number was specified.  If it was, then the slot number is decoded from the text.   At this point the slot number is a number from 1 to 10, where 10 is menu option 0.

After the slot number has been decoded, you can handle the selected menu option.

 

Keeping Track of What Menu Is Where
In most mods, multiple menus are required.  Thus, in order to handle the MENUSELECT command properly, the server needs to know which menu is being displayed on the client.  The easiest thing to do is to add a new member variable to CBasePlayer.

enum _Menu {
    Menu_Radio,
    Meun_ChangeTeam,
};

_Menu m_nMenu;

In the server, after the ShowMenu routine is invoked, set the m_nMenu member variable of the player's class instance to the menu that was displayed.   Then when the MENUSELECT command is received, the m_nMenu variable can be inspected to see which menu was displayed.

 

Menu Macros in TITLES.TXT
To reduce the amount of data sent from the server to the client, the ShowMenu system enables the developer to use macros to insert large amounts of menu text into the menu.

To send a sample 3 option menu to the user without using macros, the ShowMenu call could look like this:

ShowMenu (pPlayer, 0x7, 0, 0, "Select an Option:\n\n1. Option A\n"
    "2. Option B\n3. Option C");

Or, to send the same menu using TITLES.TXT, the ShowMenu call would look like this:

ShowMenu (pPlayer, 0x7, 0, 0, "#Menu_Sample");

When the client DLL displays a menu, it searches the menu text for the '#' character.   If found, then it extracts the name of the macro, searches the TITLES.TXT for a match, and inserts the located text into the menu.  Thus, to complete the previous example, the TITLES.TXT for the game in question would include the following lines. (DO NOT MODIFY TITLES.TXT in the VALVE directory, only in your mod's directory)

Menu_Sample
{
Select an Option:

1. Option A
2. Option B
3. Option C
}

The macro system can also be used to handle more dynamic menus.   An example would be a radio options menu where the client can turn features off.   When the client requests the radio menu, a menu string is generated dynamically to build the full menu string.

char szMenu [512];
strcpy (szMenu, "#Menu_Radio_Head\n\n");
strcat (szMenu, "#Menu_Option1_Text");
strcat (szMenu, fOp1On ? "#Menu_Off" : "#Menu_On");
ShowMenu (...);

The contents of TITLES.TXT would include:

Menu_Radio_Head
{
Select an Option:
}

Menu_Radio_Option1_Text
{
1. Radio Sound
}

Menu_Off
{
Off
}

Menu_On
{
On
}

 

Accessing Menu Items 6-0
Half-Life by default only defines the weapon slot commands for 1-5. Thus menus that have menu items in slots 6-0 will not work properly. To access these other menu items, you must bind the other keys.

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

- Tim Smith

 

blank.gif (837 bytes)
bl.jpg (2471 bytes) blank.gif (837 bytes) br.jpg (2132 bytes)