core/MenuStyle_Base.cpp
author David Anderson <dvander@alliedmods.net>
Sat Jun 16 21:42:05 2007 +0000 (2007-06-16)
changeset 959 f0231833e6ec
parent 884 a5f3239cef1a
child 970 b47becc0083d
permissions -rw-r--r--
fixed bug amb402
     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 <stdarg.h>
    16 #include "sm_stringutil.h"
    17 #include "MenuStyle_Base.h"
    18 #include "PlayerManager.h"
    19 #include "MenuManager.h"
    20 #include "HandleSys.h"
    21 
    22 BaseMenuStyle::BaseMenuStyle() : m_WatchList(256), m_hHandle(BAD_HANDLE)
    23 {
    24 }
    25 
    26 Handle_t BaseMenuStyle::GetHandle()
    27 {
    28 	/* Don't create the handle until we need it */
    29 	if (m_hHandle == BAD_HANDLE)
    30 	{
    31 		m_hHandle = g_Menus.CreateStyleHandle(this);
    32 	}
    33 
    34 	return m_hHandle;
    35 }
    36 
    37 void BaseMenuStyle::AddClientToWatch(int client)
    38 {
    39 	m_WatchList.push_back(client);
    40 }
    41 
    42 void BaseMenuStyle::RemoveClientFromWatch(int client)
    43 {
    44 	m_WatchList.remove(client);
    45 }
    46 
    47 void BaseMenuStyle::_CancelClientMenu(int client, bool bAutoIgnore/* =false */, MenuCancelReason reason/* =MenuCancel_Interrupt */)
    48 {
    49 	CBaseMenuPlayer *player = GetMenuPlayer(client);
    50 	menu_states_t &states = player->states;
    51 
    52 	bool bOldIgnore = player->bAutoIgnore;
    53 	if (bAutoIgnore)
    54 	{
    55 		player->bAutoIgnore = true;
    56 	}
    57 
    58 	/* Save states */
    59 	IMenuHandler *mh = states.mh;
    60 	IBaseMenu *menu = states.menu;
    61 
    62 	/* Clear menu */
    63 	player->bInMenu = false;
    64 	if (player->menuHoldTime)
    65 	{
    66 		RemoveClientFromWatch(client);
    67 	}
    68 
    69 	/* Fire callbacks */
    70 	mh->OnMenuCancel(menu, client, reason);
    71 	
    72 	/* Only fire end if there's a valid menu */
    73 	if (menu)
    74 	{
    75 		mh->OnMenuEnd(menu);
    76 	}
    77 
    78 	if (bAutoIgnore)
    79 	{
    80 		player->bAutoIgnore = bOldIgnore;
    81 	}
    82 }
    83 
    84 void BaseMenuStyle::CancelMenu(CBaseMenu *menu)
    85 {
    86 	int maxClients = g_Players.GetMaxClients();
    87 	for (int i=1; i<=maxClients; i++)
    88 	{
    89 		CBaseMenuPlayer *player = GetMenuPlayer(i);
    90 		if (player->bInMenu)
    91 		{
    92 			menu_states_t &states = player->states;
    93 			if (states.menu == menu)
    94 			{
    95 				_CancelClientMenu(i);
    96 			}
    97 		}
    98 	}
    99 }
   100 
   101 bool BaseMenuStyle::CancelClientMenu(int client, bool autoIgnore)
   102 {
   103 	if (client < 1 || client > g_Players.MaxClients())
   104 	{
   105 		return false;
   106 	}
   107 
   108 	CBaseMenuPlayer *player = GetMenuPlayer(client);
   109 	if (!player->bInMenu)
   110 	{
   111 		return false;
   112 	}
   113 
   114 	_CancelClientMenu(client, autoIgnore);
   115 
   116 	return true;
   117 }
   118 
   119 MenuSource BaseMenuStyle::GetClientMenu(int client, void **object)
   120 {
   121 	if (client < 1 || client > g_Players.GetMaxClients())
   122 	{
   123 		return MenuSource_None;
   124 	}
   125 
   126 	CBaseMenuPlayer *player = GetMenuPlayer(client);
   127 
   128 	if (player->bInMenu)
   129 	{
   130 		IBaseMenu *menu;
   131 		if ((menu=player->states.menu) != NULL)
   132 		{
   133 			if (object)
   134 			{
   135 				*object = menu;
   136 			}
   137 			return MenuSource_BaseMenu;
   138 		}
   139 
   140 		return MenuSource_Display;
   141 	} else if (player->bInExternMenu) {
   142 		if (player->menuHoldTime != 0
   143 			&& (gpGlobals->curtime > player->menuStartTime + player->menuHoldTime))
   144 		{
   145 			player->bInExternMenu = false;
   146 			return MenuSource_None;
   147 		}
   148 		return MenuSource_External;
   149 	}
   150 
   151 	return MenuSource_None;
   152 }
   153 
   154 void BaseMenuStyle::OnClientDisconnected(int client)
   155 {
   156 	CBaseMenuPlayer *player = GetMenuPlayer(client);
   157 	if (!player->bInMenu)
   158 	{
   159 		return;
   160 	}
   161 
   162 	_CancelClientMenu(client, true, MenuCancel_Disconnect);
   163 
   164 	player->bInMenu = false;
   165 	player->bInExternMenu = false;
   166 }
   167 
   168 static int do_lookup[256];
   169 void BaseMenuStyle::ProcessWatchList()
   170 {
   171 	if (!m_WatchList.size())
   172 	{
   173 		return;
   174 	}
   175 
   176 	unsigned int total = 0;
   177 	for (FastLink<int>::iterator iter=m_WatchList.begin(); iter!=m_WatchList.end(); ++iter)
   178 	{
   179 		do_lookup[total++] = (*iter);
   180 	}
   181 
   182 	int client;
   183 	CBaseMenuPlayer *player;
   184 	float curTime = gpGlobals->curtime;
   185 	for (unsigned int i=0; i<total; i++)
   186 	{
   187 		client = do_lookup[i];
   188 		player = GetMenuPlayer(client);
   189 		if (!player->bInMenu || !player->menuHoldTime)
   190 		{
   191 			m_WatchList.remove(client);
   192 			continue;
   193 		}
   194 		if (curTime > player->menuStartTime + player->menuHoldTime)
   195 		{
   196 			_CancelClientMenu(client, false);
   197 		}
   198 	}
   199 }
   200 
   201 void BaseMenuStyle::ClientPressedKey(int client, unsigned int key_press)
   202 {
   203 	CBaseMenuPlayer *player = GetMenuPlayer(client);
   204 
   205 	/* First question: Are we in a menu? */
   206 	if (!player->bInMenu)
   207 	{
   208 		return;
   209 	}
   210 
   211 	bool cancel = false;
   212 	unsigned int item = 0;
   213 	MenuCancelReason reason = MenuCancel_Exit;
   214 	menu_states_t &states = player->states;
   215 
   216 	assert(states.mh != NULL);
   217 
   218 	if (states.menu == NULL)
   219 	{
   220 		item = key_press;
   221 	} else if (key_press < 1 || key_press > GetMaxPageItems()) {
   222 		cancel = true;
   223 	} else {
   224 		ItemSelection type = states.slots[key_press].type;
   225 
   226 		/* For navigational items, we're going to redisplay */
   227 		if (type == ItemSel_Back)
   228 		{
   229 			if (!RedoClientMenu(client, ItemOrder_Descending))
   230 			{
   231 				cancel = true;
   232 				reason = MenuCancel_NoDisplay;
   233 			} else {
   234 				return;
   235 			}
   236 		} else if (type == ItemSel_Next) {
   237 			if (!RedoClientMenu(client, ItemOrder_Ascending))
   238 			{
   239 				cancel = true;						/* I like Saltines. */
   240 				reason = MenuCancel_NoDisplay;
   241 			} else {
   242 				return;
   243 			}
   244 		} else if (type == ItemSel_Exit || type == ItemSel_None) {
   245 			cancel = true;
   246 		} else {
   247 			item = states.slots[key_press].item;
   248 		}
   249 	}
   250 
   251 	/* Save variables */
   252 	IMenuHandler *mh = states.mh;
   253 	IBaseMenu *menu = states.menu;
   254 
   255 	/* Clear states */
   256 	player->bInMenu = false;
   257 	if (player->menuHoldTime)
   258 	{
   259 		RemoveClientFromWatch(client);
   260 	}
   261 
   262 	if (cancel)
   263 	{
   264 		mh->OnMenuCancel(menu, client, reason);
   265 	} else {
   266 		mh->OnMenuSelect(menu, client, item);
   267 	}
   268 
   269 	/* Only fire end for valid menus */
   270 	if (menu)
   271 	{
   272 		mh->OnMenuEnd(menu);
   273 	}
   274 }
   275 
   276 bool BaseMenuStyle::DoClientMenu(int client, IMenuPanel *menu, IMenuHandler *mh, unsigned int time)
   277 {
   278 	CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
   279 	if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
   280 	{
   281 		return false;
   282 	}
   283 
   284 	CBaseMenuPlayer *player = GetMenuPlayer(client);
   285 	if (player->bAutoIgnore)
   286 	{
   287 		return false;
   288 	}
   289 
   290 	/* For the duration of this, we are going to totally ignore whether
   291 	* the player is already in a menu or not (except to cancel the old one).
   292 	* Instead, we are simply going to ignore any further menu displays, so
   293 	* this display can't be interrupted.
   294 	*/
   295 	player->bAutoIgnore = true;
   296 
   297 	/* Cancel any old menus */
   298 	menu_states_t &states = player->states;
   299 	if (player->bInMenu)
   300 	{
   301 		_CancelClientMenu(client, true);
   302 	}
   303 
   304 	states.firstItem = 0;
   305 	states.lastItem = 0;
   306 	states.menu = NULL;
   307 	states.mh = mh;
   308 	states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
   309 	player->bInMenu = true;
   310 	player->bInExternMenu = false;
   311 	player->menuStartTime = gpGlobals->curtime;
   312 	player->menuHoldTime = time;
   313 
   314 	if (time)
   315 	{
   316 		AddClientToWatch(client);
   317 	}
   318 
   319 	/* Draw the display */
   320 	SendDisplay(client, menu);
   321 
   322 	/* We can be interrupted again! */
   323 	player->bAutoIgnore = false;
   324 
   325 	return true;
   326 }
   327 
   328 bool BaseMenuStyle::DoClientMenu(int client, CBaseMenu *menu, IMenuHandler *mh, unsigned int time)
   329 {
   330 	mh->OnMenuStart(menu);
   331 
   332 	if (!mh)
   333 	{
   334 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   335 		mh->OnMenuEnd(menu);
   336 		return false;
   337 	}
   338 
   339 	CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
   340 	if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
   341 	{
   342 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   343 		mh->OnMenuEnd(menu);
   344 		return false;
   345 	}
   346 
   347 	CBaseMenuPlayer *player = GetMenuPlayer(client);
   348 	if (player->bAutoIgnore)
   349 	{
   350 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   351 		mh->OnMenuEnd(menu);
   352 		return false;
   353 	}
   354 
   355 	/* For the duration of this, we are going to totally ignore whether
   356 	* the player is already in a menu or not (except to cancel the old one).
   357 	* Instead, we are simply going to ignore any further menu displays, so
   358 	* this display can't be interrupted.
   359 	*/
   360 	player->bAutoIgnore = true;
   361 
   362 	/* Cancel any old menus */
   363 	menu_states_t &states = player->states;
   364 	if (player->bInMenu)
   365 	{
   366 		_CancelClientMenu(client, true);
   367 	}
   368 
   369 	states.firstItem = 0;
   370 	states.lastItem = 0;
   371 	states.menu = menu;
   372 	states.mh = mh;
   373 	states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
   374 
   375 	IMenuPanel *display = g_Menus.RenderMenu(client, states, ItemOrder_Ascending);
   376 	if (!display)
   377 	{
   378 		player->bAutoIgnore = false;
   379 		player->bInMenu = false;
   380 		mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
   381 		mh->OnMenuEnd(menu);
   382 		return false;
   383 	}
   384 
   385 	/* Finally, set our states */
   386 	player->bInMenu = true;
   387 	player->bInExternMenu = false;
   388 	player->menuStartTime = gpGlobals->curtime;
   389 	player->menuHoldTime = time;
   390 
   391 	if (time)
   392 	{
   393 		AddClientToWatch(client);
   394 	}
   395 
   396 	/* Draw the display */
   397 	SendDisplay(client, display);
   398 
   399 	/* Free the display pointer */
   400 	display->DeleteThis();
   401 
   402 	/* We can be interrupted again! */
   403 	player->bAutoIgnore = false;
   404 
   405 	return true;
   406 }
   407 
   408 bool BaseMenuStyle::RedoClientMenu(int client, ItemOrder order)
   409 {
   410 	CBaseMenuPlayer *player = GetMenuPlayer(client);
   411 	menu_states_t &states = player->states;
   412 
   413 	player->bAutoIgnore = true;
   414 	IMenuPanel *display = g_Menus.RenderMenu(client, states, order);
   415 	if (!display)
   416 	{
   417 		if (player->menuHoldTime)
   418 		{
   419 			RemoveClientFromWatch(client);
   420 		}
   421 		player->bAutoIgnore = false;
   422 		return false;
   423 	}
   424 
   425 	SendDisplay(client, display);
   426 
   427 	display->DeleteThis();
   428 
   429 	player->bAutoIgnore = false;
   430 
   431 	return true;
   432 }
   433 
   434 CBaseMenu::CBaseMenu(IMenuHandler *pHandler, IMenuStyle *pStyle, IdentityToken_t *pOwner) : 
   435 m_pStyle(pStyle), m_Strings(512), m_Pagination(7), m_ExitButton(true), 
   436 m_bShouldDelete(false), m_bCancelling(false), m_pOwner(pOwner ? pOwner : g_pCoreIdent), 
   437 m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler),
   438 m_pVoteHandler(NULL)
   439 {
   440 }
   441 
   442 CBaseMenu::~CBaseMenu()
   443 {
   444 	g_Menus.ReleaseVoteWrapper(m_pVoteHandler);
   445 }
   446 
   447 Handle_t CBaseMenu::GetHandle()
   448 {
   449 	if (!m_hHandle)
   450 	{
   451 		m_hHandle = g_Menus.CreateMenuHandle(this, m_pOwner);
   452 	}
   453 
   454 	return m_hHandle;
   455 }
   456 
   457 bool CBaseMenu::AppendItem(const char *info, const ItemDrawInfo &draw)
   458 {
   459 	if (m_Pagination == (unsigned)MENU_NO_PAGINATION
   460 		&& m_items.size() >= m_pStyle->GetMaxPageItems())
   461 	{
   462 		return false;
   463 	}
   464 
   465 	CItem item;
   466 
   467 	item.infoString = m_Strings.AddString(info);
   468 	if (draw.display)
   469 	{
   470 		item.displayString = m_Strings.AddString(draw.display);
   471 	}
   472 	item.style = draw.style;
   473 
   474 
   475 	m_items.push_back(item);
   476 
   477 	return true;
   478 }
   479 
   480 bool CBaseMenu::InsertItem(unsigned int position, const char *info, const ItemDrawInfo &draw)
   481 {
   482 	if (m_Pagination == (unsigned)MENU_NO_PAGINATION
   483 		&& m_items.size() >= m_pStyle->GetMaxPageItems())
   484 	{
   485 		return false;
   486 	}
   487 
   488 	if (position >= m_items.size())
   489 	{
   490 		return false;
   491 	}
   492 
   493 	CItem item;
   494 	item.infoString = m_Strings.AddString(info);
   495 	if (draw.display)
   496 	{
   497 		item.displayString = m_Strings.AddString(draw.display);
   498 	}
   499 	item.style = draw.style;
   500 
   501 	CVector<CItem>::iterator iter = m_items.iterAt(position);
   502 	m_items.insert(iter, item);
   503 
   504 	return true;
   505 }
   506 
   507 bool CBaseMenu::RemoveItem(unsigned int position)
   508 {
   509 	if (position >= m_items.size())
   510 	{
   511 		return false;
   512 	}
   513 
   514 	m_items.erase(m_items.iterAt(position));
   515 
   516 	if (m_items.size() == 0)
   517 	{
   518 		m_Strings.Reset();
   519 	}
   520 
   521 	return true;
   522 }
   523 
   524 void CBaseMenu::RemoveAllItems()
   525 {
   526 	m_items.clear();
   527 	m_Strings.Reset();
   528 }
   529 
   530 const char *CBaseMenu::GetItemInfo(unsigned int position, ItemDrawInfo *draw/* =NULL */)
   531 {
   532 	if (position >= m_items.size())
   533 	{
   534 		return NULL;
   535 	}
   536 
   537 	if (draw)
   538 	{
   539 		draw->display = m_Strings.GetString(m_items[position].displayString);
   540 		draw->style = m_items[position].style;
   541 	}
   542 
   543 	return m_Strings.GetString(m_items[position].infoString);
   544 }
   545 
   546 unsigned int CBaseMenu::GetItemCount()
   547 {
   548 	return m_items.size();
   549 }
   550 
   551 bool CBaseMenu::SetPagination(unsigned int itemsPerPage)
   552 {
   553 	if (itemsPerPage > 7 || itemsPerPage == 1)
   554 	{
   555 		return false;
   556 	}
   557 
   558 	m_Pagination = itemsPerPage;
   559 
   560 	return true;
   561 }
   562 
   563 unsigned int CBaseMenu::GetPagination()
   564 {
   565 	return m_Pagination;
   566 }
   567 
   568 IMenuStyle *CBaseMenu::GetDrawStyle()
   569 {
   570 	return m_pStyle;
   571 }
   572 
   573 void CBaseMenu::SetDefaultTitle(const char *message)
   574 {
   575 	m_Title.assign(message);
   576 }
   577 
   578 const char *CBaseMenu::GetDefaultTitle()
   579 {
   580 	return m_Title.c_str();
   581 }
   582 
   583 bool CBaseMenu::GetExitButton()
   584 {
   585 	return m_ExitButton;
   586 }
   587 
   588 bool CBaseMenu::SetExitButton(bool set)
   589 {
   590 	m_ExitButton = set;
   591 	return true;
   592 }
   593 
   594 void CBaseMenu::Cancel()
   595 {
   596 	if (m_bCancelling)
   597 	{
   598 		return;
   599 	}
   600 
   601 	if (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress())
   602 	{
   603 		m_pVoteHandler->CancelVoting();
   604 	}
   605 
   606 	m_bCancelling = true;
   607 	Cancel_Finally();
   608 	m_bCancelling = false;
   609 
   610 	if (m_bShouldDelete)
   611 	{
   612 		InternalDelete();
   613 	}
   614 }
   615 
   616 void CBaseMenu::Destroy(bool releaseHandle)
   617 {
   618 	/* Check if we shouldn't be here */
   619 	if (m_bDeleting)
   620 	{
   621 		return;
   622 	}
   623 
   624 	/* Save the destruction hint about our handle */
   625 	m_bWillFreeHandle = releaseHandle;
   626 
   627 	/* Now actually do stuff */
   628 	if (!m_bCancelling || m_bShouldDelete)
   629 	{
   630 		Cancel();
   631 		InternalDelete();
   632 	} else {
   633 		m_bShouldDelete = true;
   634 	}
   635 }
   636 
   637 void CBaseMenu::InternalDelete()
   638 {
   639 	if (m_bWillFreeHandle && m_hHandle != BAD_HANDLE)
   640 	{
   641 		Handle_t hndl = m_hHandle;
   642 		HandleSecurity sec;
   643 
   644 		sec.pOwner = m_pOwner;
   645 		sec.pIdentity = g_pCoreIdent;
   646 
   647 		m_hHandle = BAD_HANDLE;
   648 		m_bDeleting = true;
   649 		g_HandleSys.FreeHandle(hndl, &sec);
   650 	}
   651 
   652 	m_pHandler->OnMenuDestroy(this);
   653 
   654 	delete this;
   655 }
   656 
   657 bool CBaseMenu::BroadcastVote(int clients[], 
   658 							  unsigned int numClients, 
   659 							  unsigned int maxTime,
   660 							  unsigned int flags)
   661 {
   662 	if (!m_pVoteHandler)
   663 	{
   664 		m_pVoteHandler = g_Menus.CreateVoteWrapper(m_pHandler);
   665 	} else if (m_pVoteHandler->IsVoteInProgress()) {
   666 		return false;
   667 	}
   668 
   669 	m_pVoteHandler->InitializeVoting(this);
   670 
   671 	for (unsigned int i=0; i<numClients; i++)
   672 	{
   673 		VoteDisplay(clients[i], maxTime);
   674 	}
   675 
   676 	m_pVoteHandler->StartVoting();
   677 
   678 	return true;
   679 }
   680 
   681 bool CBaseMenu::IsVoteInProgress()
   682 {
   683 	return (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress());
   684 }