Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 32 | Next >>

menu

  • Home/News
  • ModSource
  • Compiling
  • Help!!!
  • Submission
  • Contributors
  • Staff
  • Downloads

    Tutorials
    < Index >
    1. Mod making 101
    2. Up 'n running
    3. Hello, QWorld!
    4. Infinite Haste
    5. Armor Piercing Rails
    6. Bouncing Rockets
    7. Cloaking
    8. Ladders
    9. Favourite Server
    10. Flame Thrower
    11. Vortex Grenades
    12. Grapple
    13. Lightning Discharge
    14. Locational Damage
    15. Leg Shots
    16. Weapon Switching
    17. Scoreboard frag-rate
    18. Vortex Grenades II
    19. Vulnerable Missiles
    20. Creating Classes
    21. Scrolling Credits
    22. Weapon Dropping
    23. Anti-Gravity Boots
    24. HUD scoreboard
    25. Flashlight and laser
    26. Weapon Positioning
    27. Weapon Reloading
    28. Progressive Zooming
    29. Rotating Doors
    30. Beheading (headshot!)
    31. Alt Weapon Fire
    32. Popup Menus I
    33. Popup Menus II
    34. Cluster Grenades
    35. Homing Rockets
    36. Spreadfire Powerup
    37. Instagib gameplay
    38. Accelerating rockets
    39. Server only Instagib
    40. Advanced Grapple Hook
    41. Unlagging your mod


    Articles
    < Index >
    1. Entities
    2. Vectors
    3. Good Coding
    4. Compilers I
    5. Compilers II
    6. UI Menu Primer I
    7. UI Menu Primer II
    8. UI Menu Primer III
    9. QVM Communication, Cvars, commands
    10. Metrowerks CodeWarrior
    11. 1.27g code, bugs, batch


    Links

  • Quake3 Files
  • Quake3 Forums
  • Q3A Editing Message Board
  • Quake3 Editing


    Feedback

  • SumFuka
  • Calrathan
  • HypoThermia
  • WarZone





    Site Design by:
    ICEmosis Design


  •  
    TUTORIAL 32 - Popup Menus I
    by HypoThermia

    This tutorial is in two parts, describing a dynamic popup game menu system that can be used while the the player is in the middle of a game. This kind of menu can be used to issue commands to team mates, buy items, or set in-game options where where the Escape menu is just too cumbersome.

    The menu responds to the position of the cursor, automatically displaying or hiding a sub-menu. This makes it very easy to structure the menu and group commands together. Its also just about the quickest way to get a command out without resorting to dedicated key binds or complicated key sequencing.

    This first part covers the core menu framework system. Once implemented you'll be able to start writing your own hierarchical menus. I've tried to make the setup of these menus as simple as possible. you just need to initialize a menu, add the items, and then complete the init once all the items are in place. Then you have to write the payload for the command that'll be issued.

    The second part of the tutorial covers a detailed implementation of the bot command list. Even if you don't want to add this to your mod, there's a lot of useful information about how the User Interface (and client code) can get access to info that is stored on the server. This includes things like a list of all players, identifying bots, and items present on the map currently being played.

    One of the most important things to understand about code like this is the role of sanity checking values before using them. Any errors are going to appear at run-time, and will be that much more difficult to debug. It's very easy to access elements that are outside an array, or haven't been initialized yet. The second part of the tutorial shows how to do this by example.

    If you want to see the menu system in action, then go grab my Q3 User Interface mod UI Enhanced (shameless plug!). Those of you that have played Unreal Tournament will recognize the menu format straight away.
     

    1. Setting up data structures

    The items in the menu and sub-menus are all stored sequentially in an array. Boundaries are maintained between each menu, and there are no gaps. The array is filled from the bottom up, with dead sub-menus being removed and overwritten by their replacements.

    All the code changes, unless otherwise stated, go into ui_ingame.c, the current in-game menu source code file. You can start appending this code after the last function in that file.

    The order in which functions are placed in the file means that function declaration isn't needed. Make sure that you place the function before its first use, otherwise you'll get redeclaration warnings/errors.

    We'll start with essential data structures and constants.

    /*
    ===========================
    
    INGAME DYNAMIC COMMAND MENU
    
    ===========================
    */
    
    
    #define MAX_DYNAMICDEPTH 	6
    #define MAX_MENUSTRING 		16
    
    #define MENUSPACE_X		4
    #define MENUSPACE_Y		1
    
    
    typedef void (*createHandler)(void);
    typedef void (*eventHandler)(int index);
    
    
    typedef struct {
    	char text[MAX_MENUSTRING];
    	int index;
    	int id;
    	createHandler createSubMenu;
    	eventHandler runEvent;
    } dynamicitem_t;
    
    
    typedef struct {
    	menuframework_s menu;
    
    	menutext_s item[MAX_MENUITEMS];
    	dynamicitem_t data[MAX_MENUITEMS];
    
    	int start[MAX_DYNAMICDEPTH];
    	int end[MAX_DYNAMICDEPTH];	// indicates to (last item + 1)
    	int active[MAX_DYNAMICDEPTH];
    
    	int gametype;
    	int depth;
    } dynamicmenu_t;
    
    static dynamicmenu_t s_dynamic;
    

    MAX_DYNAMICDEPTH controls how many sub-menus are supported. With the maximum number of chars in each menu item set to MAX_MENUSTRING, in practice we can only get 4 menus on screen anyway. Make sure your items/commands are meaningful in 16 chars!

    MENUSPACE_X and MENUSPACE_Y control spacing when the menu is drawn on-screen.

    The typedefs createHandler and eventHandler are the generic functions used to create sub-menus and issue commands respectively. Any menu item can have either one or the other associated with it, and the functions called must be in this format.

    The dynamicitem_t structure carries all additional information needed to draw a menu item. index gives array position in dynamicmenu_t, while id is an identifying value for your use. createSubMenu and runEvent control how the item behaves when the mouse hovers over it, or clicks on it, respectively.

    Finally, the dynamicmenu_t contains all the info needed to draw and control the menu. MAX_MENUITEMS is set in ui_local.h, and and you might find the default value of 32 a little restrictive. It can be increased without harming any other code.

    The arrays dynamicmenu_t->start[] and dynamicmenu_t->end[] control which items are grouped with each menu. Take care to remember that dynamicmenu_t->end[] actually points to the next available free item slot, and so isn't actually initialized yet!

    dynamicmenu_t->active[] allows you to track back through the sub-menus and see which items have been activated, very useful for building up commands based on earlier menu selections. Its also used to draw a highlight under the activated menu items.

    The dynamicmenu_t->gametype is set very early on, and allows menu code to check the gametype without having to grab the g_gametype Cvar.

    Care needs to be used with dynamicmenu_t->depth, it actually counts the number of open menus (so the very first menu created has a depth of 1). In many parts of the code you'll see (depth-1) to convert this back to a zero based array. This convention must be understood, otherwise you'll access un-initialized data!
     

    2. Dynamic menu creation and initialization

    With these data structures in place we now need to add the means to set them up for use. There are three steps:

    1. Initialize for a sub-menu
    2. Add each of the menu items
    3. Prepare the menu for drawing on screen

    Starting with initialization, we only need to call this once for a new menu. It returns qfalse if the init fails for any reason.

    The very first menu starts at the beginning of the array, while sub-menus follow on at the first free slot pointed to by s_dynamic.end[]. No menu item is yet active, so we set a safe value, and init the range of items in this menu.

    /*
    =================
    DynamicMenu_InitSubMenu
    =================
    */
    static qboolean DynamicMenu_SubMenuInit( void)
    {
    	int pos;
    
    	if (s_dynamic.depth == MAX_DYNAMICDEPTH)
    		return qfalse;
    
    	if (s_dynamic.depth == 0)
    		pos = 0;
    	else
    		pos = s_dynamic.end[s_dynamic.depth - 1];
    
    	if (pos == MAX_MENUITEMS)
    		return qfalse;
    
    	s_dynamic.depth++;
    	s_dynamic.active[s_dynamic.depth - 1] = -1;
    	s_dynamic.start[s_dynamic.depth - 1] = pos;
    	s_dynamic.end[s_dynamic.depth - 1] = pos;
    
    	return qtrue;
    }
    

     

    For each item in the menu we need to give the string that'll be drawn, and a unique id for the create or event handler to use (if needed). This id is stored in the dynamicitem_t->id, as the generic.id value in the menutext_s items are already in use (see below).

    The action that the item will take is also set here, by supplying the required handler function. These functions must be in the format of the typdefs that define them (createHandler and eventHandler).

    If we try and overflow the arrays reserved for us, the extra items are quietly dropped. This prevents a possible crash in the framework code by an overly large menu, at the expense of not drawing all the menu items. Increasing MAX_MENUITEMS will fix this.

    An id value only needs to be unique for any single createHandler or eventHandler function. There is no requirement to make the id unique across all your menu handler functions.

    /*
    =================
    DynamicMenu_AddItem
    =================
    */
    static qboolean DynamicMenu_AddItem( const char* string, 
    	int id, createHandler crh, eventHandler evh)
    {
    	int index, depth;
    
    	depth = s_dynamic.depth - 1;
    	index = s_dynamic.end[depth];
    
    	if (index == MAX_MENUITEMS)
    		return qfalse;
    
    	// can't have submenu and event attached to menu item	
    	if (crh && evh)
    		return qfalse;
    
    	if (!string || !string[0])
    		string = "[no text]";
    
    	s_dynamic.data[index].index = index;
    	s_dynamic.data[index].id = id;
    	s_dynamic.data[index].createSubMenu = crh;
    	s_dynamic.data[index].runEvent = evh;
    	Q_strncpyz(s_dynamic.data[index].text, string, MAX_MENUSTRING);
    
    	s_dynamic.end[depth]++;
    
    	return qtrue;
    }
    

     

    Finally, we need to pay attention to all the details we couldn't handle while creating the menu.

    The width of the menu is set, and space is created (if needed) to draw a marker that indicates a sub-menu will pop-up. This marker is a special character in the Q3 font, a right pointing arrow head. Its referenced by the character '\r', I picked it up from the menu code for radio buttons.

    We then need to set the position of each menu item on the screen. The very first menu is easy: centered halfway up the screen on the left edge. After that it becomes a little more complicated. Ideally we'd like the first menu item to be level with the item the cursor is hovering over, so we try and set the height to that value. Unfortunately this might push the menu off the bottom of the screen, so we bump it up if it might do this. No error checking for the top of the screen... just don't make your menus too large!

    Finally we set the screen position and cursor hit area, and allow the control to be drawn on screen. Note that this code doesn't wipe out the QMF_GRAYED flag. This allows it to be set by your own code during the AddItem phase. See an example for this in part II of this tutorial.

    /*
    =================
    DynamicMenu_FinishInitSubMenu
    =================
    */
    static void DynamicMenu_FinishSubMenuInit( void )
    {
    	int depth;
    	int width, maxwidth;
    	int height, lineheight;
    	int posx, posy;
    	int i, count, start, active;
    	float scale;
    	menutext_s* ptr;
    	qboolean submenu;
    
    	depth = s_dynamic.depth - 1;
    
    	// find the widest item
    	submenu = qfalse;
    	maxwidth = 0;
    	start = s_dynamic.start[depth];
    	count = s_dynamic.end[depth] - start;
    	for ( i = 0; i < count; i++)
    	{
    		width = UI_ProportionalStringWidth(s_dynamic.data[i + start].text);
    		if (width > maxwidth)
    			maxwidth = width;
    
    		if (s_dynamic.data[i + start].createSubMenu)
    			submenu = qtrue;	
    	}
    
    	scale = UI_ProportionalSizeScale(UI_SMALLFONT);
    	if (submenu)
    	{
    		// space and submenu pointer
    		maxwidth += UI_ProportionalStringWidth(" \r");
    	}
    
    	maxwidth *= scale;
    
    	// determine the position of the menu
    	lineheight = PROP_HEIGHT * scale + 2*MENUSPACE_Y;
    	height = count * lineheight;
    
    	if (depth == 0)
    	{
    		posy = 240 - height/2;
    		posx = 0;
    	}
    	else
    	{
    		active = s_dynamic.active[depth - 1];
    		posx = s_dynamic.item[active].generic.right;
    		posy = s_dynamic.item[active].generic.top;
    
    		if (posy + height > 480 - 64)
    			posy = 480 - 64 - height;
    	}
    
    	for (i = 0; i < count; i++)
    	{
    		ptr = &s_dynamic.item[start + i];
    
    		ptr->generic.x = posx + MENUSPACE_X;
    		ptr->generic.y = posy + i*lineheight + MENUSPACE_Y;
    
    		ptr->generic.left = posx;
    		ptr->generic.right = posx + maxwidth + 2*MENUSPACE_X;
    		ptr->generic.top = posy + i*lineheight;
    		ptr->generic.bottom = posy + (i+1)*lineheight - 1;
    
    		ptr->generic.flags &= ~(QMF_HIDDEN|QMF_INACTIVE);
    	}
    }
    

     

    3. Drawing the menu item

    These are custom drawn menu items, consisting of red text on a translucent white box background. Each box leaves a slight gap with the box above and below.

    A little helper function detects whether an item is on the active list, leading to a slightly brighter background being drawn instead. The right arrow head is also drawn to indicate the presence of a sub-menu.

    I've also provided a custom draw function for the entire menu. It has some useful debugging info commented out, you can add to this or use it as needed.

    /*
    =================
    DynamicMenu_OnActiveList
    =================
    */
    static qboolean DynamicMenu_OnActiveList( int index )
    {
    	int depth;
    	int i;
    
    	depth = s_dynamic.depth;
    
    	for ( i = 0; i < depth ; i++)
    		if (s_dynamic.active[i] == index)
    			return qtrue;
    			
    	return qfalse;
    }
    
    
    
    
    /*
    =================
    DynamicMenu_MenuItemDraw
    =================
    */
    static void DynamicMenu_MenuItemDraw( void* self )
    {
    	int		x;
    	int		y;
    
    	int		w,h;
    	float *	color;
    	int		style;
    	menutext_s* t;
    	vec4_t	back_color;
    
    	t = (menutext_s*)self;
    
    
    	// draw the background;
    	x = t->generic.left;
    	y = t->generic.top;
    	w = t->generic.right - x;
    	h = t->generic.bottom - y;
    
    	back_color[0] = 1.0;
    	back_color[1] = 1.0;
    	back_color[2] = 1.0;
    	if (DynamicMenu_OnActiveList(t->generic.id))
    	{
    		back_color[3] = 0.33;
    	}
    	else
    	{
    		back_color[3] = 0.1;
    	}
    
    	UI_FillRect(x, y, w, h, back_color);
    
    	// draw the text
    	x = t->generic.x;
    	y = t->generic.y;
    
    	if (t->generic.flags & QMF_GRAYED)
    		color = text_color_disabled;
    	else
    		color = t->color;
    
    	style = t->style;
    	if( t->generic.flags & QMF_PULSEIFFOCUS ) {
    		if( Menu_ItemAtCursor( t->generic.parent ) == t ) {
    			style |= UI_PULSE;
    		}
    		else {
    			style |= UI_INVERSE;
    		}
    	}
    
    	UI_DrawProportionalString( x, y, t->string, style, color );
    
    	// draw the cursor for submenu if needed
    	x = t->generic.left + w;
    	if (s_dynamic.data[ t->generic.id ].createSubMenu)
    	{
    		UI_DrawChar( x, y, 13, style|UI_RIGHT, color);
    	}
    }
    
    
    
    
    /*
    =================
    DynamicMenu_MenuDraw
    =================
    */
    static void DynamicMenu_MenuDraw( void )
    {
    //	UI_DrawString(0, 0, va("depth:%i", s_dynamic.depth), 
    //		UI_SMALLFONT, color_white);
    //	UI_DrawString(0, 32, va("active: %i %i %i", 
    //		s_dynamic.active[0], s_dynamic.active[1], s_dynamic.active[2] ),
    //		UI_SMALLFONT, color_white);
    
    	Menu_Draw(&s_dynamic.menu);
    }
    

     

    4. Menu event handling

    We'll now start to bring the menu to life, with functions that handle mouse events created by movement of the cursor.

    There are three possible events that a control can receive:

    QM_GOTFOCUS
    When the cursor moves over the hit area for a control

    QM_LOSTFOCUS
    When the cursor leaves teh hit area

    QM_ACTIVATED
    when the mouse button is pressed

    The QM_LOSTFOCUS message isn't used, but a placeholder function DynamicMenu_ClearFocus() is provided in case you need to use it.

    When the focus is set on a menu item, DynamicMenu_SetFocus() closes all submenus at a greater depth. The QMF_GRAYED flag is stripped at this point, so any future menu items don't inherit it. If the item opens a sub-menu then that sub-menu is prepared and created.

    When activated, DynamicMenu_ActivateControl() calls the event handler that issues the command associated with the menu item.

    Every menu event in the QM_* family is handled through DynamicMenu_MenuEvent().

    It's expected that once the command has been issued, the menu will be closed. This should be done by calling DynamicMenu_Close(), and every command should do this. Why do it this way? Well in UI Enhanced I've implemented this function slightly differently, so that a UI Cvar decides whether UI_PopMenu() is called. This allows multiple commands to be issued without closing the menu. The user can decide whether they want to do this or have the menu close each time a command is completed. (an trivial exercise for the reader!)

    The last remaining function is DynamicMenu_IndexDepth(), it identifies the depth of the item just selected (this could be at any depth in the menu structure). If it returns a value of zero then something has gone wrong somewhere, and we don't have a valid menu item.

    /*
    =================
    DynamicMenu_IndexDepth
    =================
    */
    static int DynamicMenu_IndexDepth( int pos )
    {
    	int i;
    	int maxdepth, depth;
    
    	maxdepth = s_dynamic.depth;
    	depth = 0;
    	for (i = 0; i < maxdepth; i++)
    	{
    		if (pos < s_dynamic.end[i])
    		{
    			depth = i + 1;
    			break;
    		}
    	}
    
    	return depth;
    }
    
    
    
    /*
    =================
    DynamicMenu_SetFocus
    =================
    */
    static void DynamicMenu_SetFocus( int pos )
    {
    	int i;
    	int depth, newdepth;
    
    	depth = s_dynamic.depth;
    	newdepth = DynamicMenu_IndexDepth(pos);
    
    	if (newdepth == 0)
    	{
    		Com_Printf("SetFocus: index %i outside menu\n", pos);
    		return;
    	}
    
    	s_dynamic.active[ newdepth - 1 ] = pos;
    	s_dynamic.depth = newdepth;
    
    	// hide any previous submenus
    	if (newdepth < depth)
    	{
    		for (i = s_dynamic.start[ newdepth ]; 
    			i < s_dynamic.end[depth - 1]; i++)
    		{
    			s_dynamic.item[i].generic.flags |= (QMF_HIDDEN|QMF_INACTIVE);
    			s_dynamic.item[i].generic.flags &= ~QMF_GRAYED;
    		}
    	}
    
    	s_dynamic.active[newdepth - 1] = pos;
    
    	// show this sub-menu (if needed)
    	if (s_dynamic.data[pos].createSubMenu)
    		s_dynamic.data[pos].createSubMenu();
    }
    
    
    /*
    =================
    DynamicMenu_ClearFocus
    =================
    */
    static void DynamicMenu_ClearFocus( int pos )
    {
    }
    
    
    
    /*
    =================
    DynamicMenu_ActivateControl
    =================
    */
    static void DynamicMenu_ActivateControl( int pos )
    {
    	int i;
    	int depth;
    
    	depth = DynamicMenu_IndexDepth(pos);
    
    	if (depth == 0)
    	{
    		Com_Printf("ActivateControl: index %i outside menu\n", pos);
    		return;
    	}
    
    	// not at the deepest level, can't be a command
    	if (depth < s_dynamic.depth)
    		return;
    
    	if (s_dynamic.data[pos].runEvent)
    		s_dynamic.data[pos].runEvent(pos);
    	else
    		Com_Printf("ActivateControl: index %i has no event\n", pos);
    }
    
    
    
    
    /*
    =================
    DynamicMenu_MenuEvent
    =================
    */
    static void DynamicMenu_MenuEvent( void* self, int event )
    {
    	menutext_s* t;
    
    	t = (menutext_s*)self;
    
    	switch (event)
    	{
    	case QM_GOTFOCUS:
    		DynamicMenu_SetFocus(t->generic.id);
    		break;
    	case QM_LOSTFOCUS:
    		DynamicMenu_ClearFocus(t->generic.id);
    		break;
    	case QM_ACTIVATED:
    		DynamicMenu_ActivateControl(t->generic.id);
    		break;
    	}
    }
    
    
    
    
    
    /*
    =================
    DynamicMenu_Close
    =================
    */
    static void DynamicMenu_Close( void )
    {
    	UI_PopMenu();
    }
    

     

    5. Initializing the menu controls

    With all of the dynamic initialization out of the way, we still need to prepare the menu for use. Fortunately we only need to do this once, just like the standard static menu code used in the rest of the User Interface.

    There are several sections of code that are commented out. These comments need to be removed if you're adding the second part of this tutorial as well.

    The DynamicMenu_MenuInit() function does most of the work here. Each displayed control needs to be connected to the owner draw and event handler function. Although the text displayed for each control might changed, the pointer to the text buffer won't, so this is also set. The use of QMF_NODEFAULTINIT is essential, as we set up and provide these values ourselves.

    When it comes to drawing the menu on screen, using menu.fullscreen = qfalse prevents a black background being drawn and the rest of the game being paused. Action will continue around the player, even in the single player game against the bots.

    Although no graphics are used that need caching, a placeholder UI_DynamicMenuCache() function is provided.

    UI_DynamicMenu() is the entry point into the dynamic menu, and kicks the whole process off. This is the best place to check for, and reject, the creation of the menu. Two useful examples are provided: checks for the player as spectator, and game type.

    Finally, we have UI_DynamicCommandMenu_f(), which is called by the key bind code that we also need to add.

    /*
    =================
    DynamicMenu_MenuInit
    =================
    */
    static void DynamicMenu_MenuInit( void )
    {
    	int i;
    
    	s_dynamic.menu.draw = DynamicMenu_MenuDraw;
    	s_dynamic.menu.fullscreen = qfalse;
    	s_dynamic.menu.wrapAround = qfalse;
    
    	for (i = 0; i < MAX_MENUITEMS; i++)
    	{
    		s_dynamic.item[i].generic.type = MTYPE_PTEXT;
    		s_dynamic.item[i].generic.flags = QMF_INACTIVE
    			|QMF_HIDDEN|QMF_NODEFAULTINIT|QMF_PULSEIFFOCUS;
    		s_dynamic.item[i].generic.ownerdraw = DynamicMenu_MenuItemDraw ;
    		s_dynamic.item[i].generic.callback = DynamicMenu_MenuEvent ;
    		s_dynamic.item[i].generic.id = i;
    		s_dynamic.item[i].string = s_dynamic.data[i].text;
    		s_dynamic.item[i].style = UI_SMALLFONT|UI_DROPSHADOW;
    		s_dynamic.item[i].color = color_red;
    
    		Menu_AddItem(&s_dynamic.menu, &s_dynamic.item[i]);
    	}
    
    	// start up the menu system
    	s_dynamic.depth = 0;
    
    //	Uncomment the next line if adding part II as well
    //	DynamicMenu_InitMapItems();
    
    	DynamicMenu_InitPrimaryMenu();
    }
    
    
    
    
    
    
    /*
    =================
    UI_DynamicMenuCache
    =================
    */
    void UI_DynamicMenuCache( void )
    {
    }
    
    
    
    
    /*
    =================
    UI_DynamicMenu
    =================
    */
    void UI_DynamicMenu( void )
    {
    	uiClientState_t	cs;
    	char	info[MAX_INFO_STRING];
    	int	playerTeam;
    
    	trap_GetClientState( &cs );
    	trap_GetConfigString( CS_PLAYERS 
    		+ cs.clientNum, info, MAX_INFO_STRING );
    	playerTeam = atoi(Info_ValueForKey(info, "t"));
    
    //	Uncomment the next two code lines if adding part II 
    //	as well, or specs can't use the menu either
    //	if (playerTeam == TEAM_SPECTATOR)
    //		return;
    
    	memset(&s_dynamic.menu, 0, sizeof(dynamicmenu_t));
    
    	s_dynamic.gametype = (int)trap_Cvar_VariableValue("g_gametype");
    
    //	Uncomment the next three lines if adding part II as well
    //	if (s_dynamic.gametype != GT_TEAM && 
    //			s_dynamic.gametype != GT_CTF)
    //		return;
    
    	UI_DynamicMenuCache();
    
    	// force as top level menu
    	uis.menusp = 0;
    
    	// set menu cursor to a nice location
    	uis.cursorx = 50;
    	uis.cursory = 240;
    
    	DynamicMenu_MenuInit();
    
    	UI_PushMenu( &s_dynamic.menu );
    }
    
    
    
    /*
    =================
    UI_DynamicCommandMenu_f
    =================
    */
    void UI_DynamicCommandMenu_f( void )
    {
    	UI_DynamicMenu();
    }
    

     

    With this code done, this almost completes the framework for the menu system. The remaining changes are in two different source files:

    In ui_local.h, around line 307, we need to add a declaration for several functions so they can be seen by the rest of the code:

    //
    // ui_ingame.c
    //
    extern void InGame_Cache( void );
    extern void UI_InGameMenu(void);
    extern void UI_DynamicMenuCache(void);
    extern void UI_DynamicMenu( void );
    extern void UI_BotCommandMenu_f( void );
    

     

    The other change is in ui_atoms.c, in UI_ConsoleCommand(), where we add a new command to display our menu. You can choose any appropriate command name, and this will become the key bind used to create the menu.

    You might also want to modify the code in ui_controls.c so that the key bind can be set from within the game, without using the console.

    if ( Q_stricmp (cmd, "ui_cinematics") == 0 ) {
    	UI_CinematicsMenu_f();
    	return qtrue;
    }
    
    if ( Q_stricmp (cmd, "ui_dynamicmenu") == 0 ) {
    	UI_DynamicCommandMenu_f();
    	return qtrue;
    }
    
    

     

    6. A trivial example to get you started

    That's done everything needed for the framework, although if you've compiled the code you'll find that there's still an error. This is for the function DynamicMenu_InitPrimaryMenu(), which starts by creating the top level menu.

    This little example includes that function (you'll want to provide your own of course!) All it does is add an item that closes the menu just opened, but you can see how the dynamic menu is initialized, item(s) are added, and the menu completed, ready for drawing on screen.

    This one example doesn't use DynamicMenu_Close() for the simple reason that we want to guarantee closure of the menu. If you've made the changes I described earlier then the command might not close the menu.

    You should place this code just before the DynamicMenu_MenuInit(void) function.

    /*
    =================
    DM_Close_Event
    =================
    */
    static void DM_Close_Event( int index )
    {
    	UI_PopMenu();
    }
    
    
    /*
    =================
    DynamicMenu_InitPrimaryMenu
    =================
    */
    static void DynamicMenu_InitPrimaryMenu( void )
    {
    	DynamicMenu_SubMenuInit();
    
    	DynamicMenu_AddItem("Close!", 0, NULL, DM_Close_Event);
    
    	DynamicMenu_FinishSubMenuInit();
    }
    

     

    7. Framework completed!

    With all the code above in place, you're now in a position to go a write your own dynamic menus.

    I'd suggest that you go and read the second part of this tutorial, even if you don't implement it. It contains a very detailed implementation of all the bot commands, and shows how all of this framework is used to full effect. There are also a large number of helper functions that get access to data from the User Interface, data that's normally associated with the server.

    PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 32 | Next >>