core/MenuStyle_Valve.cpp
author David Anderson <dvander@alliedmods.net>
Sun May 13 00:42:39 2007 +0000 (2007-05-13)
changeset 774 67b8b99bad61
parent 772 ff6662122b97
child 775 9c537df13ec8
permissions -rw-r--r--
fixed some re-entrancy bugs
removed debug commands
     1 /**
     2  * vim: set ts=4 :
     3  * ===============================================================
     4  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     5  * ===============================================================
     6  *
     7  * This file is not open source and may not be copied without explicit
     8  * written permission of AlliedModders LLC.  This file may not be redistributed 
     9  * in whole or significant part.
    10  * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
    11  *
    12  * Version: $Id$
    13  */
    14 
    15 #include "sm_stringutil.h"
    16 #include "PlayerManager.h"
    17 #include "MenuStyle_Valve.h"
    18 #include "Translator.h"
    19 #include "PlayerManager.h"
    20 #include "ConCmdManager.h"
    21 
    22 SH_DECL_HOOK4_void(IServerPluginHelpers, CreateMessage, SH_NOATTRIB, false, edict_t *, DIALOG_TYPE, KeyValues *, IServerPluginCallbacks *);
    23 
    24 ValveMenuStyle g_ValveMenuStyle;
    25 const char *g_OptionNumTable[];
    26 const char *g_OptionCmdTable[];
    27 IServerPluginCallbacks *g_pVSPHandle = NULL;
    28 CallClass<IServerPluginHelpers> *g_pSPHCC = NULL;
    29 
    30 class TestHandler : public IMenuHandler
    31 {
    32 public:
    33 	virtual void OnMenuEnd(IBaseMenu *menu)
    34 	{
    35 		menu->Destroy();
    36 	}
    37 };
    38 
    39 ValveMenuStyle::ValveMenuStyle() : m_players(new CValveMenuPlayer[256+1]), m_WatchList(256)
    40 {
    41 }
    42 
    43 bool ValveMenuStyle::OnClientCommand(int client)
    44 {
    45 	const char *cmd = engine->Cmd_Argv(0);
    46 
    47 	if (strcmp(cmd, "sm_vmenuselect") == 0)
    48 	{
    49 		int key_press = atoi(engine->Cmd_Argv(1));
    50 		g_ValveMenuStyle.ClientPressedKey(client, key_press);
    51 		return true;
    52 	}
    53 
    54 	return false;
    55 }
    56 
    57 void ValveMenuStyle::OnSourceModAllInitialized()
    58 {
    59 	g_Players.AddClientListener(this);
    60 	SH_ADD_HOOK_MEMFUNC(IServerPluginHelpers, CreateMessage, serverpluginhelpers, this, &ValveMenuStyle::HookCreateMessage, false);
    61 	g_pSPHCC = SH_GET_CALLCLASS(serverpluginhelpers);
    62 }
    63 
    64 void ValveMenuStyle::OnSourceModShutdown()
    65 {
    66 	SH_RELEASE_CALLCLASS(g_pSPHCC);
    67 	SH_REMOVE_HOOK_MEMFUNC(IServerPluginHelpers, CreateMessage, serverpluginhelpers, this, &ValveMenuStyle::HookCreateMessage, false);
    68 	g_Players.RemoveClientListener(this);
    69 }
    70 
    71 void ValveMenuStyle::OnClientDisconnected(int client)
    72 {
    73 	CValveMenuPlayer *player = &m_players[client];
    74 	if (!player->bInMenu)
    75 	{
    76 		return;
    77 	}
    78 
    79 	_CancelMenu(client, true, MenuCancel_Disconnect);
    80 
    81 	player->bInMenu = false;
    82 }
    83 
    84 void ValveMenuStyle::HookCreateMessage(edict_t *pEdict,
    85 									   DIALOG_TYPE type,
    86 									   KeyValues *kv,
    87 									   IServerPluginCallbacks *plugin)
    88 {
    89 	if (type != DIALOG_MENU)
    90 	{
    91 		return;
    92 	}
    93 
    94 	int client = engine->IndexOfEdict(pEdict);
    95 	if (client < 1 || client > 256)
    96 	{
    97 		return;
    98 	}
    99 
   100 	CValveMenuPlayer *player = &m_players[client];
   101 	
   102 	/* We don't care if the player is in a menu because, for all intents and purposes,
   103 	 * the menu is completely private.  Instead, we just figure out the level we'll need
   104 	 * in order to override it.
   105 	 */
   106 	player->curPrioLevel = kv->GetInt("level", player->curPrioLevel);
   107 
   108 	/* Oh no! What happens if we got a menu that overwrites ours?! */
   109 	if (player->bInMenu)
   110 	{
   111 		/* Okay, let the external menu survive for now.  It may live another 
   112 		 * day to avenge its grandfather, killed in the great Menu Interruption
   113 		 * battle.
   114 		 */
   115 		_CancelMenu(client, true);
   116 	}
   117 }
   118 
   119 void ValveMenuStyle::OnSourceModVSPReceived(IServerPluginCallbacks *iface)
   120 {
   121 	g_pVSPHandle = iface;
   122 }
   123 
   124 IMenuDisplay *ValveMenuStyle::CreateDisplay()
   125 {
   126 	return new CValveMenuDisplay();
   127 }
   128 
   129 IBaseMenu *ValveMenuStyle::CreateMenu()
   130 {
   131 	return new CValveMenu();
   132 }
   133 
   134 const char *ValveMenuStyle::GetStyleName()
   135 {
   136 	return "valve";
   137 }
   138 
   139 unsigned int ValveMenuStyle::GetMaxPageItems()
   140 {
   141 	return 8;
   142 }
   143 
   144 static int do_lookup[256];
   145 void ValveMenuStyle::ProcessWatchList()
   146 {
   147 	if (!m_WatchList.size())
   148 	{
   149 		return;
   150 	}
   151 
   152 	unsigned int total = 0;
   153 	for (FastLink<int>::iterator iter=m_WatchList.begin(); iter!=m_WatchList.end(); ++iter)
   154 	{
   155 		do_lookup[total++] = (*iter);
   156 	}
   157 
   158 	int client;
   159 	CValveMenuPlayer *player;
   160 	float curTime = gpGlobals->curtime;
   161 	for (unsigned int i=0; i<total; i++)
   162 	{
   163 		client = do_lookup[i];
   164 		player = &m_players[client];
   165 		if (!player->bInMenu || !player->menuHoldTime)
   166 		{
   167 			m_WatchList.remove(i);
   168 			continue;
   169 		}
   170 		if (curTime > player->menuStartTime + player->menuHoldTime)
   171 		{
   172 			_CancelMenu(client, false);
   173 		}
   174 	}
   175 }
   176 
   177 void ValveMenuStyle::_CancelMenu(int client, bool bAutoIgnore, MenuCancelReason reason)
   178 {
   179 	CValveMenuPlayer *player = &m_players[client];
   180 	menu_states_t &states = player->states;
   181 
   182 	bool bOldIgnore = player->bAutoIgnore;
   183 	if (bAutoIgnore)
   184 	{
   185 		player->bAutoIgnore = true;
   186 	}
   187 
   188 	/* Save states */
   189 	IMenuHandler *mh = states.mh;
   190 	IBaseMenu *menu = states.menu;
   191 
   192 	/* Clear menu */
   193 	player->bInMenu = false;
   194 	if (player->menuHoldTime)
   195 	{
   196 		m_WatchList.remove(client);
   197 	}
   198 
   199 	/* Fire callbacks */
   200 	mh->OnMenuCancel(menu, client, reason);
   201 	mh->OnMenuEnd(menu);
   202 
   203 	if (bAutoIgnore)
   204 	{
   205 		player->bAutoIgnore = bOldIgnore;
   206 	}
   207 }
   208 
   209 void ValveMenuStyle::CancelMenu(CValveMenu *menu)
   210 {
   211 	int maxClients = g_Players.GetMaxClients();
   212 	for (int i=1; i<=maxClients; i++)
   213 	{
   214 		if (m_players[i].bInMenu)
   215 		{
   216 			menu_states_t &states = m_players[i].states;
   217 			if (states.menu == menu)
   218 			{
   219 				_CancelMenu(i);
   220 			}
   221 		}
   222 	}
   223 }
   224 
   225 bool ValveMenuStyle::CancelClientMenu(int client, bool autoIgnore)
   226 {
   227 	if (client < 1 || client > 256 || !m_players[client].bInMenu)
   228 	{
   229 		return false;
   230 	}
   231 
   232 	_CancelMenu(client, autoIgnore);
   233 
   234 	return true;
   235 }
   236 
   237 
   238 void ValveMenuStyle::ClientPressedKey(int client, unsigned int key_press)
   239 {
   240 	CValveMenuPlayer *player = &m_players[client];
   241 
   242 	/* First question: Are we in a menu? */
   243 	if (!player->bInMenu)
   244 	{
   245 		return;
   246 	}
   247 
   248 	bool cancel = false;
   249 	unsigned int item = 0;
   250 	MenuCancelReason reason = MenuCancel_Exit;
   251 	menu_states_t &states = player->states;
   252 
   253 	assert(states.mh != NULL);
   254 
   255 	if (states.menu == NULL)
   256 	{
   257 		item = key_press;
   258 	} else if (key_press < 1 ||  key_press > 8) {
   259 		cancel = true;
   260 	} else {
   261 		ItemSelection type = states.slots[key_press].type;
   262 
   263 		/* For navigational items, we're going to redisplay */
   264 		if (type == ItemSel_Back)
   265 		{
   266 			if (!RedoClientMenu(client, ItemOrder_Descending))
   267 			{
   268 				cancel = true;
   269 				reason = MenuCancel_NoDisplay;
   270 			} else {
   271 				return;
   272 			}
   273 		} else if (type == ItemSel_Next) {
   274 			if (!RedoClientMenu(client, ItemOrder_Ascending))
   275 			{
   276 				cancel = true;						/* I like Saltines. */
   277 				reason = MenuCancel_NoDisplay;
   278 			} else {
   279 				return;
   280 			}
   281 		} else if (type == ItemSel_Exit || type == ItemSel_None) {
   282 			cancel = true;
   283 		} else {
   284 			item = states.slots[key_press].item;
   285 		}
   286 	}
   287 
   288 	/* Save variables */
   289 	IMenuHandler *mh = states.mh;
   290 	IBaseMenu *menu = states.menu;
   291 
   292 	/* Clear states */
   293 	player->bInMenu = false;
   294 	if (player->menuHoldTime)
   295 	{
   296 		m_WatchList.remove(client);
   297 	}
   298 
   299 	if (cancel)
   300 	{
   301 		mh->OnMenuCancel(menu, client, reason);
   302 	} else {
   303 		mh->OnMenuSelect(menu, client, item);
   304 	}
   305 
   306 	mh->OnMenuEnd(menu);
   307 }
   308 
   309 bool ValveMenuStyle::DoClientMenu(int client, CValveMenuDisplay *menu, IMenuHandler *mh, unsigned int time)
   310 {
   311 	if (!g_pVSPHandle || !mh)
   312 	{
   313 		return false;
   314 	}
   315 
   316 	CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
   317 	if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
   318 	{
   319 		return false;
   320 	}
   321 
   322 	CValveMenuPlayer *player = &m_players[client];
   323 	if (player->bAutoIgnore)
   324 	{
   325 		return false;
   326 	}
   327 
   328 	/* For the duration of this, we are going to totally ignore whether
   329 	 * the player is already in a menu or not (except to cancel the old one).
   330 	 * Instead, we are simply going to ignore any further menu displays, so
   331 	 * this display can't be interrupted.
   332 	 */
   333 	player->bAutoIgnore = true;
   334 
   335 	/* Cancel any old menus */
   336 	menu_states_t &states = player->states;
   337 	if (player->bInMenu)
   338 	{
   339 		/* We need to cancel the old menu */
   340 		if (player->menuHoldTime)
   341 		{
   342 			m_WatchList.remove(client);
   343 		}
   344 		states.mh->OnMenuCancel(states.menu, client, MenuCancel_Interrupt);
   345 		states.mh->OnMenuEnd(states.menu);
   346 	}
   347 
   348 	states.firstItem = 0;
   349 	states.lastItem = 0;
   350 	states.menu = NULL;
   351 	states.mh = mh;
   352 	states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
   353 	player->curPrioLevel--;
   354 	player->bInMenu = true;
   355 	player->menuStartTime = gpGlobals->curtime;
   356 	player->menuHoldTime = time;
   357 
   358 	if (time)
   359 	{
   360 		m_WatchList.push_back(client);
   361 	}
   362 
   363 	/* Draw the display */
   364 	menu->SendRawDisplay(client, player->curPrioLevel, time);
   365 
   366 	/* We can be interrupted again! */
   367 	player->bAutoIgnore = false;
   368 
   369 	return true;
   370 }
   371 
   372 bool ValveMenuStyle::DoClientMenu(int client, CValveMenu *menu, IMenuHandler *mh, unsigned int time)
   373 {
   374 	mh->OnMenuStart(menu);
   375 
   376 	if (!g_pVSPHandle || !mh)
   377 	{
   378 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   379 		mh->OnMenuEnd(menu);
   380 		return false;
   381 	}
   382 
   383 	CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
   384 	if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
   385 	{
   386 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   387 		mh->OnMenuEnd(menu);
   388 		return false;
   389 	}
   390 
   391 	CValveMenuPlayer *player = &m_players[client];
   392 	if (player->bAutoIgnore)
   393 	{
   394 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   395 		mh->OnMenuEnd(menu);
   396 		return false;
   397 	}
   398 
   399 	/* For the duration of this, we are going to totally ignore whether
   400 	 * the player is already in a menu or not (except to cancel the old one).
   401 	 * Instead, we are simply going to ignore any further menu displays, so
   402 	 * this display can't be interrupted.
   403 	 */
   404 	player->bAutoIgnore = true;
   405 
   406 	/* Cancel any old menus */
   407 	menu_states_t &states = player->states;
   408 	if (player->bInMenu)
   409 	{
   410 		_CancelMenu(client, true);
   411 	}
   412 
   413 	states.firstItem = 0;
   414 	states.lastItem = 0;
   415 	states.menu = menu;
   416 	states.mh = mh;
   417 	states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
   418 
   419 	IMenuDisplay *display = g_Menus.RenderMenu(client, states, ItemOrder_Ascending);
   420 	if (!display)
   421 	{
   422 		player->bAutoIgnore = false;
   423 		player->bInMenu = false;
   424 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   425 		mh->OnMenuEnd(menu);
   426 		return false;
   427 	}
   428 
   429 	/* Finally, set our states */
   430 	player->curPrioLevel--;
   431 	player->bInMenu = true;
   432 	player->menuStartTime = gpGlobals->curtime;
   433 	player->menuHoldTime = time;
   434 
   435 	if (time)
   436 	{
   437 		m_WatchList.push_back(client);
   438 	}
   439 
   440 	/* Draw the display */
   441 	CValveMenuDisplay *vDisplay = (CValveMenuDisplay *)display;
   442 	vDisplay->SendRawDisplay(client, player->curPrioLevel, time);
   443 
   444 	/* Free the display pointer */
   445 	delete display;
   446 
   447 	/* We can be interrupted again! */
   448 	player->bAutoIgnore = false;
   449 
   450 	return true;
   451 }
   452 
   453 bool ValveMenuStyle::RedoClientMenu(int client, ItemOrder order)
   454 {
   455 	CValveMenuPlayer *player = &m_players[client];
   456 	menu_states_t &states = player->states;
   457 
   458 	player->bAutoIgnore = true;
   459 	IMenuDisplay *display = g_Menus.RenderMenu(client, states, order);
   460 	if (!display)
   461 	{
   462 		if (player->menuHoldTime)
   463 		{
   464 			m_WatchList.remove(client);
   465 		}
   466 		player->bAutoIgnore = false;
   467 		return false;
   468 	}
   469 
   470 	CValveMenuDisplay *vDisplay = (CValveMenuDisplay *)display;
   471 	vDisplay->SendRawDisplay(client, --player->curPrioLevel, player->menuHoldTime);
   472 
   473 	delete display;
   474 
   475 	player->bAutoIgnore = false;
   476 
   477 	return true;
   478 }
   479 
   480 MenuSource ValveMenuStyle::GetClientMenu(int client, void **object)
   481 {
   482 	if (client < 1 || client > 256 || !m_players[client].bInMenu)
   483 	{
   484 		return MenuSource_None;
   485 	}
   486 
   487 	IBaseMenu *menu;
   488 	if ((menu=m_players[client].states.menu) != NULL)
   489 	{
   490 		if (object)
   491 		{
   492 			*object = menu;
   493 		}
   494 		return MenuSource_BaseMenu;
   495 	}
   496 
   497 	return MenuSource_Display;
   498 }
   499 
   500 CValveMenuDisplay::CValveMenuDisplay()
   501 {
   502 	m_pKv = NULL;
   503 	Reset();
   504 }
   505 
   506 CValveMenuDisplay::CValveMenuDisplay(CValveMenu *pMenu)
   507 {
   508 	m_pKv = NULL;
   509 	Reset();
   510 	m_pKv->SetColor("color", pMenu->m_IntroColor);
   511 	m_pKv->SetString("title", pMenu->m_IntroMsg);
   512 }
   513 
   514 CValveMenuDisplay::~CValveMenuDisplay()
   515 {
   516 	m_pKv->deleteThis();
   517 }
   518 
   519 IMenuStyle *CValveMenuDisplay::GetParentStyle()
   520 {
   521 	return &g_ValveMenuStyle;
   522 }
   523 
   524 void CValveMenuDisplay::Reset()
   525 {
   526 	if (m_pKv)
   527 	{
   528 		m_pKv->deleteThis();
   529 	}
   530 	m_pKv = new KeyValues("menu");
   531 	m_NextPos = 1;
   532 	m_TitleDrawn = false;
   533 }
   534 
   535 bool CValveMenuDisplay::SetExtOption(MenuOption option, const void *valuePtr)
   536 {
   537 	if (option == MenuOption_IntroMessage)
   538 	{
   539 		m_pKv->SetString("title", (const char *)valuePtr);
   540 		return true;
   541 	} else if (option == MenuOption_IntroColor) {
   542 		int *array = (int *)valuePtr;
   543 		m_pKv->SetColor("color", Color(array[0], array[1], array[2], array[3]));
   544 		return true;
   545 	}
   546 
   547 	return false;
   548 }
   549 
   550 bool CValveMenuDisplay::CanDrawItem(unsigned int drawFlags)
   551 {
   552 	/**
   553 	 * ITEMDRAW_RAWLINE - We can't draw random text, and this doesn't add a slot,
   554 	 *  so it's completely safe to ignore it.
   555 	 * -----------------------------------------
   556 	 */
   557 	if (drawFlags & ITEMDRAW_RAWLINE)
   558 	{
   559 		return false;
   560 	}
   561 
   562 	/**
   563 	 * Special cases, explained in DrawItem()
   564 	 */
   565 	if ((drawFlags & ITEMDRAW_NOTEXT)
   566 		|| (drawFlags & ITEMDRAW_SPACER))
   567 	{
   568 		return true;
   569 	}
   570 
   571 	/**
   572 	 * We can't draw disabled text.  We could bump the position, but we
   573 	 * want DirectDraw() to find some actual items to display.
   574 	 */
   575 	if (drawFlags & ITEMDRAW_DISABLED)
   576 	{
   577 		return false;
   578 	}
   579 
   580 	return true;
   581 }
   582 
   583 unsigned int CValveMenuDisplay::DrawItem(const ItemDrawInfo &item)
   584 {
   585 	if (m_NextPos > 9 || !CanDrawItem(item.style))
   586 	{
   587 		return 0;
   588 	}
   589 
   590 	/**
   591 	 * For these cases we can't draw anything at all, but
   592 	 * we can at least bump the position since we were explicitly asked to.
   593 	 */
   594 	if ((item.style & ITEMDRAW_NOTEXT)
   595 		|| (item.style & ITEMDRAW_SPACER))
   596 	{
   597 		return m_NextPos++;
   598 	}
   599 
   600 	char buffer[255];
   601 	UTIL_Format(buffer, sizeof(buffer), "%d. %s", m_NextPos, item.display);
   602 
   603 	KeyValues *ki = m_pKv->FindKey(g_OptionNumTable[m_NextPos], true);
   604 	ki->SetString("command", g_OptionCmdTable[m_NextPos]);
   605 	ki->SetString("msg", buffer);
   606 
   607 	return m_NextPos++;
   608 }
   609 
   610 void CValveMenuDisplay::DrawTitle(const char *text, bool onlyIfEmpty/* =false */)
   611 {
   612 	if (onlyIfEmpty && m_TitleDrawn)
   613 	{
   614 		return;
   615 	}
   616 
   617 	m_pKv->SetString("msg", text);
   618 	m_TitleDrawn = true;
   619 }
   620 
   621 bool CValveMenuDisplay::DrawRawLine(const char *rawline)
   622 {
   623 	return false;
   624 }
   625 
   626 void CValveMenuDisplay::SendRawDisplay(int client, int priority, unsigned int time)
   627 {
   628 	m_pKv->SetInt("level", priority);
   629 	m_pKv->SetInt("time", time);
   630 
   631 	SH_CALL(g_pSPHCC, &IServerPluginHelpers::CreateMessage)(
   632 		engine->PEntityOfEntIndex(client),
   633 		DIALOG_MENU,
   634 		m_pKv,
   635 		g_pVSPHandle);
   636 }
   637 
   638 bool CValveMenuDisplay::SendDisplay(int client, IMenuHandler *handler, unsigned int time)
   639 {
   640 	return g_ValveMenuStyle.DoClientMenu(client, this, handler, time);
   641 }
   642 
   643 CValveMenu::CValveMenu() : CBaseMenu(&g_ValveMenuStyle), 
   644 	m_IntroColor(255, 0, 0, 255), m_bShouldDelete(false), m_bCancelling(false)
   645 {
   646 	strcpy(m_IntroMsg, "You have a menu, press ESC");
   647 	m_Pagination = 5;
   648 }
   649 
   650 void CValveMenu::Cancel()
   651 {
   652 	if (m_bCancelling)
   653 	{
   654 		return;
   655 	}
   656 
   657 	m_bCancelling = true;
   658 	g_ValveMenuStyle.CancelMenu(this);
   659 	m_bCancelling = false;
   660 
   661 	if (m_bShouldDelete)
   662 	{
   663 		delete this;
   664 	}
   665 }
   666 
   667 void CValveMenu::Destroy()
   668 {
   669 	if (!m_bCancelling || m_bShouldDelete)
   670 	{
   671 		Cancel();
   672 		delete this;
   673 	} else {
   674 		m_bShouldDelete = true;
   675 	}
   676 }
   677 
   678 bool CValveMenu::SetPagination(unsigned int itemsPerPage)
   679 {
   680 	if (itemsPerPage < 1 || itemsPerPage > 5)
   681 	{
   682 		return false;
   683 	}
   684 
   685 	CBaseMenu::SetPagination(itemsPerPage);
   686 
   687 	return true;
   688 }
   689 
   690 bool CValveMenu::SetExtOption(MenuOption option, const void *valuePtr)
   691 {
   692 	if (option == MenuOption_IntroMessage)
   693 	{
   694 		strncopy(m_IntroMsg, (const char *)valuePtr, sizeof(m_IntroMsg));
   695 		return true;
   696 	} else if (option == MenuOption_IntroColor) {
   697 		unsigned int *array = (unsigned int *)valuePtr;
   698 		m_IntroColor = Color(array[0], array[1], array[2], array[3]);
   699 		return true;
   700 	}
   701 
   702 	return false;
   703 }
   704 
   705 bool CValveMenu::Display(int client, IMenuHandler *handler, unsigned int time)
   706 {
   707 	if (m_bCancelling)
   708 	{
   709 		return false;
   710 	}
   711 
   712 	return g_ValveMenuStyle.DoClientMenu(client, this, handler, time);
   713 }
   714 
   715 IMenuDisplay *CValveMenu::CreateDisplay()
   716 {
   717 	return new CValveMenuDisplay(this);
   718 }
   719 
   720 bool CValveMenu::GetExitButton()
   721 {
   722 	return true;
   723 }
   724 
   725 bool CValveMenu::SetExitButton(bool set)
   726 {
   727 	return false;
   728 }
   729 
   730 static const char *g_OptionNumTable[11] = 
   731 {
   732 	"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
   733 };
   734 
   735 static const char *g_OptionCmdTable[11] = 
   736 {
   737 	"sm_vmenuselect 0", /* INVALID! */
   738 	"sm_vmenuselect 1",
   739 	"sm_vmenuselect 2",
   740 	"sm_vmenuselect 3",
   741 	"sm_vmenuselect 4",
   742 	"sm_vmenuselect 5",
   743 	"sm_vmenuselect 6",
   744 	"sm_vmenuselect 7",
   745 	"sm_vmenuselect 8",
   746 	"sm_vmenuselect 9",
   747 	"sm_vmenuselect 10"
   748 };