extensions/topmenus/TopMenu.cpp
author David Anderson <dvander@alliedmods.net>
Tue Oct 16 20:10:43 2007 +0000 (2007-10-16)
changeset 1595 dbffbe95cb75
parent 1589 1ac5df1857fd
child 1634 241bf59d8f4d
permissions -rw-r--r--
fixed topmenus bug
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod Sample Extension
     5  * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <stdlib.h>
    33 #include <stdarg.h>
    34 #include "TopMenu.h"
    35 
    36 struct obj_by_name_t
    37 {
    38 	unsigned int obj_index;
    39 	char name[64];
    40 };
    41 
    42 int _SortObjectNamesDescending(const void *ptr1, const void *ptr2);
    43 unsigned int strncopy(char *dest, const char *src, size_t count);
    44 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
    45 
    46 TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks)
    47 {
    48 	m_clients = NULL;
    49 	m_SerialNo = 1;
    50 	m_pTitle = callbacks;
    51 	m_max_clients = 0;
    52 
    53 	if (playerhelpers->IsServerActivated())
    54 	{
    55 		CreatePlayers(playerhelpers->GetMaxClients());
    56 	}
    57 }
    58 
    59 TopMenu::~TopMenu()
    60 {
    61 	/* Delete all categories */
    62 	while (m_Categories.size())
    63 	{
    64 		RemoveFromMenu(m_Categories[0]->obj->object_id);
    65 	}
    66 
    67 	/* Remove all objects */
    68 	for (size_t i = 0; i < m_Objects.size(); i++)
    69 	{
    70 		assert(m_Objects[i]->is_free == true);
    71 		delete m_Objects[i];
    72 	}
    73 
    74 	m_pTitle->OnTopMenuObjectRemoved(this, 0);
    75 
    76 	/* Delete all cached config entries */
    77 	for (size_t i = 0; i < m_Config.cats.size(); i++)
    78 	{
    79 		delete m_Config.cats[i];
    80 	}
    81 
    82 	/* Sweep players */
    83 	for (size_t i = 0; i <= (size_t)m_max_clients; i++)
    84 	{
    85 		TearDownClient(&m_clients[i]);
    86 	}
    87 	delete [] m_clients;
    88 }
    89 
    90 void TopMenu::OnClientConnected(int client)
    91 {
    92 	if (m_clients == NULL)
    93 	{
    94 		return;
    95 	}
    96 
    97 	topmenu_player_t *player = &m_clients[client];
    98 	TearDownClient(player);
    99 }
   100 
   101 void TopMenu::OnClientDisconnected(int client)
   102 {
   103 	if (m_clients == NULL)
   104 	{
   105 		return;
   106 	}
   107 
   108 	topmenu_player_t *player = &m_clients[client];
   109 	TearDownClient(player);
   110 }
   111 
   112 void TopMenu::OnServerActivated(int max_clients)
   113 {
   114 	if (m_clients == NULL)
   115 	{
   116 		CreatePlayers(max_clients);
   117 	}
   118 }
   119 
   120 unsigned int TopMenu::AddToMenu(const char *name,
   121 								TopMenuObjectType type,
   122 								ITopMenuObjectCallbacks *callbacks,
   123 								IdentityToken_t *owner,
   124 								const char *cmdname,
   125 								FlagBits flags,
   126 								unsigned int parent)
   127 {
   128 	/* Sanity checks */
   129 	if (type == TopMenuObject_Category && parent != 0)
   130 	{
   131 		return 0;
   132 	}
   133 	else if (type == TopMenuObject_Item && parent == 0)
   134 	{
   135 		return 0;
   136 	}
   137 	else if (m_ObjLookup.retrieve(name) != NULL)
   138 	{
   139 		return 0;
   140 	}
   141 	else if (type != TopMenuObject_Item && type != TopMenuObject_Category)
   142 	{
   143 		return 0;
   144 	}
   145 
   146 	/* If we're adding an item, make sure the parent is valid, 
   147 	 * and that the parent is a category.
   148 	 */
   149 	topmenu_object_t *parent_obj = NULL;
   150 	topmenu_category_t *parent_cat = NULL;
   151 	if (type == TopMenuObject_Item)
   152 	{
   153 		/* Check parent index.  Note it will be >= 1 here. */
   154 		if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free)
   155 		{
   156 			return 0;
   157 		}
   158 		parent_obj = m_Objects[parent - 1];
   159 
   160 		/* Find an equivalent pointer in the category array. */
   161 		for (size_t i = 0; i < m_Categories.size(); i++)
   162 		{
   163 			if (m_Categories[i]->obj == parent_obj)
   164 			{
   165 				parent_cat = m_Categories[i];
   166 				break;
   167 			}
   168 		}
   169 
   170 		/* If none was found, leave. */
   171 		if (parent_cat == NULL)
   172 		{
   173 			return 0;
   174 		}
   175 	}
   176 
   177 	/* Re-use an old object pointer if we can. */
   178 	topmenu_object_t *obj = NULL;
   179 	for (size_t i = 0; i < m_Objects.size(); i++)
   180 	{
   181 		if (m_Objects[i]->is_free == true)
   182 		{
   183 			obj = m_Objects[i];
   184 			break;
   185 		}
   186 	}
   187 
   188 	/* Otherwise, allocate a new one. */
   189 	if (obj == NULL)
   190 	{
   191 		obj = new topmenu_object_t;
   192 		obj->object_id = ((unsigned int)m_Objects.size()) + 1;
   193 		m_Objects.push_back(obj);
   194 	}
   195 
   196 	/* Initialize the object's properties. */
   197 	obj->callbacks = callbacks;
   198 	obj->flags = flags;
   199 	obj->owner = owner;
   200 	obj->type = type;
   201 	obj->is_free = false;
   202 	obj->parent = parent_obj;
   203 	strncopy(obj->name, name, sizeof(obj->name));
   204 	strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname));
   205 
   206 	if (obj->type == TopMenuObject_Category)
   207 	{
   208 		/* Create a new category entry */
   209 		topmenu_category_t *cat = new topmenu_category_t;
   210 		cat->obj = obj;
   211 		cat->reorder = false;
   212 		cat->serial = 1;
   213 
   214 		/* Add it, then update our serial change number. */
   215 		m_Categories.push_back(cat);
   216 		m_SerialNo++;
   217 
   218 		/* Updating sorting info */
   219 		m_bCatsNeedResort = true;
   220 	}
   221 	else if (obj->type == TopMenuObject_Item)
   222 	{
   223 		/* Update the category, mark it as needing changes */
   224 		parent_cat->obj_list.push_back(obj);
   225 		parent_cat->reorder = true;
   226 		parent_cat->serial++;
   227 
   228 		/* If the category just went from 0 to 1 items, mark it as 
   229 		 * changed, so clients get the category drawn.
   230 		 */
   231 		if (parent_cat->obj_list.size() == 1)
   232 		{
   233 			m_SerialNo++;
   234 		}
   235 	}
   236 
   237 	m_ObjLookup.insert(name, obj);
   238 
   239 	return obj->object_id;
   240 }
   241 
   242 void TopMenu::RemoveFromMenu(unsigned int object_id)
   243 {
   244 	if (object_id == 0 
   245 		|| object_id > m_Objects.size() 
   246 		|| m_Objects[object_id - 1]->is_free)
   247 	{
   248 		return;
   249 	}
   250 
   251 	topmenu_object_t *obj = m_Objects[object_id - 1];
   252 
   253 	m_ObjLookup.remove(obj->name);
   254 
   255 	if (obj->type == TopMenuObject_Category)
   256 	{
   257 		/* Find it in the category list. */
   258 		for (size_t i = 0; i < m_Categories.size(); i++)
   259 		{
   260 			if (m_Categories[i]->obj == obj)
   261 			{
   262 				/* Mark all children as removed + free.  Note we could
   263 				 * call into RemoveMenuItem() for this, but it'd be very 
   264 				 * inefficient!
   265 				 */
   266 				topmenu_category_t *cat = m_Categories[i];
   267 				for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++)
   268 				{
   269 					cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id);
   270 					cat->obj_list[j]->is_free = true;
   271 				}
   272 				
   273 				/* Remove the category from the list, then delete it. */
   274 				m_Categories.erase(m_Categories.iterAt(i));
   275 				delete cat;
   276 				break;
   277 			}
   278 		}
   279 
   280 		/* Update the root as changed. */
   281 		m_SerialNo++;
   282 		m_bCatsNeedResort = true;
   283 	}
   284 	else if (obj->type == TopMenuObject_Item)
   285 	{
   286 		/* Find the category this item is in. */
   287 		topmenu_category_t *parent_cat = NULL;
   288 		for (size_t i = 0; i < m_Categories.size(); i++)
   289 		{
   290 			if (m_Categories[i]->obj == obj->parent)
   291 			{
   292 				parent_cat = m_Categories[i];
   293 				break;
   294 			}
   295 		}
   296 
   297 		/* Erase it from the category's lists. */
   298 		if (parent_cat)
   299 		{
   300 			for (size_t i = 0; i < parent_cat->obj_list.size(); i++)
   301 			{
   302 				if (parent_cat->obj_list[i] == obj)
   303 				{
   304 					parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i));
   305 
   306 					/* If this category now has no items, mark root as changed 
   307 					 * so clients won't get the category drawn anymore.
   308 					 */
   309 					if (parent_cat->obj_list.size() == 0)
   310 					{
   311 						m_SerialNo++;
   312 					}
   313 					break;
   314 				}
   315 			}
   316 
   317 			/* Update the category as changed. */
   318 			parent_cat->reorder = true;
   319 			parent_cat->serial++;
   320 		}
   321 	}
   322 
   323 	/* Finally, mark the object as free. */
   324 	obj->is_free = true;
   325 
   326 	/* The callbacks pointer is still valid, so fire away! */
   327 	obj->callbacks->OnTopMenuObjectRemoved(this, object_id);
   328 }
   329 
   330 bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position)
   331 {
   332 	if (m_clients == NULL)
   333 	{
   334 		return false;
   335 	}
   336 
   337 	IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
   338 	if (!pPlayer->IsInGame())
   339 	{
   340 		return false;
   341 	}
   342 
   343 	UpdateClientRoot(client, pPlayer);
   344 
   345 	topmenu_player_t *pClient = &m_clients[client];
   346 	if (pClient->root == NULL)
   347 	{
   348 		return false;
   349 	}
   350 
   351 	bool return_value = false;
   352 
   353 	if (position == TopMenuPosition_LastCategory && 
   354 		pClient->last_category < m_Categories.size())
   355 	{
   356 		return_value = DisplayCategory(client, pClient->last_category, hold_time, true);
   357 		if (!return_value)
   358 		{
   359 			return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
   360 		}
   361 	}
   362 	else if (position == TopMenuPosition_LastRoot)
   363 	{
   364 		pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
   365 	}
   366 	else if (position == TopMenuPosition_Start)
   367 	{
   368 		pClient->last_position = 0;
   369 		pClient->last_category = 0;
   370 		return_value = pClient->root->Display(client, hold_time);
   371 	}
   372 
   373 	return return_value;
   374 }
   375 
   376 bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position)
   377 {
   378 	UpdateClientCategory(client, category);
   379 
   380 	topmenu_player_t *pClient = &m_clients[client];
   381 	if (category >= pClient->cat_count
   382 		|| pClient->cats[category].menu == NULL)
   383 	{
   384 		return false;
   385 	}
   386 
   387 	bool return_value = false;
   388 
   389 	topmenu_player_category_t *player_cat = &(pClient->cats[category]);
   390 
   391 	pClient->last_category = category;
   392 	if (last_position)
   393 	{
   394 		return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position);
   395 	}
   396 	else
   397 	{
   398 		return_value = player_cat->menu->Display(client, hold_time);
   399 	}
   400 
   401 	return return_value;
   402 }
   403 
   404 void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
   405 {
   406 	const char *item_name = menu->GetItemInfo(item, NULL);
   407 	if (!item_name)
   408 	{
   409 		return;
   410 	}
   411 
   412 	topmenu_object_t *obj;
   413 	topmenu_player_t *pClient = &m_clients[client];
   414 	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
   415 	if (pObject == NULL)
   416 	{
   417 		return;
   418 	}
   419 
   420 	obj = *pObject;
   421 
   422 	/* We now have the object... what do we do with it? */
   423 	if (obj->type == TopMenuObject_Category)
   424 	{
   425 		/* If it's a category, the user wants to view it.. */
   426 		for (size_t i = 0; i < m_Categories.size(); i++)
   427 		{
   428 			if (m_Categories[i]->obj == obj)
   429 			{
   430 				pClient->last_root_pos = item_on_page;
   431 				if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false))
   432 				{
   433 					/* If we can't display the category, re-display the root menu */
   434 					DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot);
   435 				}
   436 				break;
   437 			}
   438 		}
   439 	}
   440 	else
   441 	{
   442 		pClient->last_position = item_on_page;
   443 		
   444 		/* Re-check access in case this user had their credentials revoked */
   445 		if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
   446 		{
   447 			DisplayMenu(client, 0, TopMenuPosition_LastCategory);
   448 			return;
   449 		}
   450 		
   451 		/* Pass the information on to the callback */
   452 		obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id);
   453 	}
   454 }
   455 
   456 void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
   457 {
   458 	const char *item_name = menu->GetItemInfo(item, NULL);
   459 	if (!item_name)
   460 	{
   461 		return;
   462 	}
   463 
   464 	topmenu_object_t *obj;
   465 	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
   466 	if (pObject == NULL)
   467 	{
   468 		return;
   469 	}
   470 
   471 	obj = *pObject;
   472 
   473 	style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id);
   474 	if (style != ITEMDRAW_DEFAULT)
   475 	{
   476 		return;
   477 	}
   478 
   479 	if (obj->cmdname[0] == '\0')
   480 	{
   481 		return;
   482 	}
   483 
   484 	if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
   485 	{
   486 		style = ITEMDRAW_IGNORE;
   487 	}
   488 }
   489 
   490 unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu,
   491 								int client,
   492 								IMenuPanel *panel,
   493 								unsigned int item,
   494 								const ItemDrawInfo &dr)
   495 {
   496 	const char *item_name = menu->GetItemInfo(item, NULL);
   497 	if (!item_name)
   498 	{
   499 		return 0;
   500 	}
   501 
   502 	topmenu_object_t *obj;
   503 	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
   504 	if (pObject == NULL)
   505 	{
   506 		return 0;
   507 	}
   508 
   509 	obj = *pObject;
   510 
   511 	/* Ask the object to render the text for this client */
   512 	char renderbuf[64];
   513 	obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf));
   514 
   515 	/* Build the new draw info */
   516 	ItemDrawInfo new_dr = dr;
   517 	new_dr.display = renderbuf;
   518 
   519 	/* Man I love the menu API.  Ask the panel to draw the item and give the position 
   520 	 * back to Core's renderer.  This way we don't have to worry about keeping the 
   521 	 * render buffer static!
   522 	 */
   523 	return panel->DrawItem(new_dr);
   524 }
   525 
   526 void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
   527 {
   528 	if (reason == MenuCancel_ExitBack)
   529 	{
   530 		/* If the client chose exit back, they were on a category menu, and we can
   531 		 * now display the root menu from the last known position.
   532 		 */
   533 		DisplayMenu(client, 0, TopMenuPosition_LastRoot);
   534 	}
   535 }
   536 
   537 void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer)
   538 {
   539 	topmenu_player_t *pClient = &m_clients[client];
   540 	IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client);
   541 
   542 	/* Determine if an update is necessary */
   543 	bool is_update_needed = false;
   544 	if (pClient->menu_serial != m_SerialNo)
   545 	{
   546 		is_update_needed = true;
   547 	}
   548 	else if (pPlayer->GetUserId() != pClient->user_id)
   549 	{
   550 		is_update_needed = true;
   551 	}
   552 
   553 	/* If no update is needed at the root level, just leave now */
   554 	if (!is_update_needed)
   555 	{
   556 		return;
   557 	}
   558 
   559 	/* First we need to flush the cache... */
   560 	TearDownClient(pClient);
   561 
   562 	/* Now, rebuild the category list, but don't create menus */
   563 	if (m_Categories.size() == 0)
   564 	{
   565 		pClient->cat_count = 0;
   566 		pClient->cats = NULL;
   567 	}
   568 	else
   569 	{
   570 		pClient->cat_count = (unsigned int)m_Categories.size();
   571 		pClient->cats = new topmenu_player_category_t[pClient->cat_count];
   572 		memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count);
   573 	}
   574 
   575 	/* Re-sort the root categories if needed */
   576 	SortCategoriesIfNeeded();
   577 
   578 	/* Build the root menu */
   579 	IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
   580 
   581 	/* Add the sorted items */
   582 	for (size_t i = 0; i < m_SortedCats.size(); i++)
   583 	{
   584 		if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0)
   585 		{
   586 			continue;
   587 		}
   588 		root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo(""));
   589 	}
   590 
   591 	/* Now we need to handle un-sorted items.  This is slightly trickier, as we need to 
   592 	 * pre-render each category name, and cache those names.  Phew!
   593 	 */
   594 	if (m_UnsortedCats.size())
   595 	{
   596 		obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()];
   597 		for (size_t i = 0; i < m_UnsortedCats.size(); i++)
   598 		{
   599 			obj_by_name_t *temp_obj = &item_list[i];
   600 			topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj;
   601 			obj->callbacks->OnTopMenuDisplayOption(this,
   602 				client, 
   603 				obj->object_id,
   604 				temp_obj->name,
   605 				sizeof(temp_obj->name));
   606 			temp_obj->obj_index = m_UnsortedCats[i];
   607 		}
   608 
   609 		/* Sort our temp list */
   610 		qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
   611 
   612 		/* Add the new sorted categories */
   613 		for (size_t i = 0; i < m_UnsortedCats.size(); i++)
   614 		{
   615 			if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0)
   616 			{
   617 				continue;
   618 			}
   619 			root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo(""));
   620 		}
   621 
   622 		delete [] item_list;
   623 	}
   624 
   625 	/* Set the menu's title */
   626 	char renderbuf[128];
   627 	m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf));
   628 	root_menu->SetDefaultTitle(renderbuf);
   629 
   630 	/* The client is now fully updated */
   631 	pClient->root = root_menu;
   632 	pClient->user_id = pPlayer->GetUserId();
   633 	pClient->menu_serial = m_SerialNo;
   634 	pClient->last_position = 0;
   635 	pClient->last_category = 0;
   636 	pClient->last_root_pos = 0;
   637 }
   638 
   639 void TopMenu::UpdateClientCategory(int client, unsigned int category)
   640 {
   641 	/* Update the client's root menu just in case it needs it.  This 
   642 	 * will validate that we have both a valid client and a valid
   643 	 * category structure for that client.
   644 	 */
   645 	UpdateClientRoot(client);
   646 
   647 	/* Now it's guaranteed that our category tables will be usable */
   648 	topmenu_player_t *pClient = &m_clients[client];
   649 	topmenu_category_t *cat = m_Categories[category];
   650 	topmenu_player_category_t *player_cat = &(pClient->cats[category]);
   651 
   652 	/* Does the category actually need updating? */
   653 	if (player_cat->serial == cat->serial)
   654 	{
   655 		return;
   656 	}
   657 
   658 	/* Destroy any existing menu */
   659 	if (player_cat->menu)
   660 	{
   661 		player_cat->menu->Destroy();
   662 		player_cat->menu = NULL;
   663 	}
   664 
   665 	if (pClient->last_category == category)
   666 	{
   667 		pClient->last_position = 0;
   668 	}
   669 
   670 	IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
   671 	
   672 	/* Categories get an "exit back" button */
   673 	cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK);
   674 
   675 	/* Re-sort the category if needed */
   676 	SortCategoryIfNeeded(category);
   677 
   678 	/* Build the menu with the sorted items first */
   679 	for (size_t i = 0; i < cat->sorted.size(); i++)
   680 	{
   681 		cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo(""));
   682 	}
   683 
   684 	/* Now handle unsorted items */
   685 	if (cat->unsorted.size())
   686 	{
   687 		/* Build a list of the item names */
   688 		obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()];
   689 		for (size_t i = 0; i < cat->unsorted.size(); i++)
   690 		{
   691 			obj_by_name_t *item = &item_list[i];
   692 			topmenu_object_t *obj = cat->unsorted[i];
   693 			obj->callbacks->OnTopMenuDisplayOption(this,
   694 				client,
   695 				obj->object_id,
   696 				item->name,
   697 				sizeof(item->name));
   698 			item->obj_index = (unsigned int)i;
   699 		}
   700 
   701 		/* Sort the names */
   702 		qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
   703 
   704 		/* Add to the menu */
   705 		for (size_t i = 0; i < cat->unsorted.size(); i++)
   706 		{
   707 			cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo(""));
   708 		}
   709 
   710 		delete [] item_list;
   711 	}
   712 
   713 	/* Set the menu's title */
   714 	char renderbuf[128];
   715 	cat->obj->callbacks->OnTopMenuDisplayTitle(this, 
   716 		client, 
   717 		cat->obj->object_id, 
   718 		renderbuf, 
   719 		sizeof(renderbuf));
   720 	cat_menu->SetDefaultTitle(renderbuf);
   721 
   722 	/* We're done! */
   723 	player_cat->menu = cat_menu;
   724 	player_cat->serial = cat->serial;
   725 }
   726 
   727 void TopMenu::SortCategoryIfNeeded(unsigned int category)
   728 {
   729 	topmenu_category_t *cat = m_Categories[category];
   730 	if (!cat->reorder)
   731 	{
   732 		return;
   733 	}
   734 
   735 	cat->sorted.clear();
   736 	cat->unsorted.clear();
   737 
   738 	if (cat->obj_list.size() == 0)
   739 	{
   740 		cat->reorder = false;
   741 		return;
   742 	}
   743 
   744 	CVector<unsigned int> to_sort;
   745 	for (size_t i = 0; i < cat->obj_list.size(); i++)
   746 	{
   747 		to_sort.push_back(i);
   748 	}
   749 
   750 	/* Find a matching category in the configs */
   751 	config_category_t *config_cat = NULL; 
   752 	for (size_t i = 0; i < m_Config.cats.size(); i++)
   753 	{
   754 		if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0)
   755 		{
   756 			config_cat = m_Config.cats[i];
   757 			break;
   758 		}
   759 	}
   760 
   761 	/* If there is a matching category, build a pre-sorted item list */
   762 	if (config_cat != NULL)
   763 	{
   764 		/* Find matching objects in this category */
   765 		for (size_t i = 0; i < config_cat->commands.size(); i++)
   766 		{
   767 			const char *config_name = m_Config.strings.GetString(config_cat->commands[i]);
   768 			for (size_t j = 0; j < to_sort.size(); j++)
   769 			{
   770 				if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0)
   771 				{
   772 					/* Place in the final list, then remove from the temporary list */
   773 					cat->sorted.push_back(cat->obj_list[to_sort[j]]);
   774 					to_sort.erase(to_sort.iterAt(j));
   775 					break;
   776 				}
   777 			}
   778 		}
   779 	}
   780 
   781 	/* Push any remaining items onto the unsorted list */
   782 	for (size_t i = 0; i < to_sort.size(); i++)
   783 	{
   784 		cat->unsorted.push_back(cat->obj_list[to_sort[i]]);
   785 	}
   786 
   787 	cat->reorder = false;
   788 }
   789 
   790 void TopMenu::SortCategoriesIfNeeded()
   791 {
   792 	if (!m_bCatsNeedResort)
   793 	{
   794 		return;
   795 	}
   796 
   797 	/* Clear sort results */
   798 	m_SortedCats.clear();
   799 	m_UnsortedCats.clear();
   800 
   801 	if (m_Categories.size() == 0)
   802 	{
   803 		m_bCatsNeedResort = false;
   804 		return;
   805 	}
   806 
   807 	CVector<unsigned int> to_sort;
   808 	for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++)
   809 	{
   810 		to_sort.push_back(i);
   811 	}
   812 
   813 	/* If we have any predefined categories, add them in as they appear. */
   814 	for (size_t i= 0; i < m_Config.cats.size(); i++)
   815 	{
   816 		/* Find this category and map it in if we can */
   817 		for (size_t j = 0; j < to_sort.size(); j++)
   818 		{
   819 			if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), 
   820 					   m_Categories[to_sort[j]]->obj->name) == 0)
   821 			{
   822 				/* Add to the real list and remove from the temporary */
   823 				m_SortedCats.push_back(to_sort[j]);
   824 				to_sort.erase(to_sort.iterAt(j));
   825 				break;
   826 			}
   827 		}
   828 	}
   829 
   830 	/* Push any remaining items onto the unsorted list */
   831 	for (size_t i = 0; i < to_sort.size(); i++)
   832 	{
   833 		m_UnsortedCats.push_back(to_sort[i]);
   834 	}
   835 
   836 	m_bCatsNeedResort = false;
   837 }
   838 
   839 void TopMenu::CreatePlayers(int max_clients)
   840 {
   841 	m_max_clients = max_clients;
   842 	m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1));
   843 	memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1));
   844 }
   845 
   846 void TopMenu::TearDownClient(topmenu_player_t *player)
   847 {
   848 	if (player->cats != NULL)
   849 	{
   850 		for (unsigned int i = 0; i < player->cat_count; i++)
   851 		{
   852 			topmenu_player_category_t *player_cat = &(player->cats[i]);
   853 			if (player_cat->menu != NULL)
   854 			{
   855 				player_cat->menu->Destroy();
   856 			}
   857 		}
   858 		delete [] player->cats;
   859 	}
   860 
   861 	if (player->root != NULL)
   862 	{
   863 		player->root->Destroy();
   864 	}
   865 
   866 	memset(player, 0, sizeof(topmenu_player_t));
   867 }
   868 
   869 bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength)
   870 {
   871 	SMCParseError err;
   872 	unsigned int line = 0, col = 0;
   873 
   874 	if ((err = textparsers->ParseFile_SMC(file, this, &line, &col))
   875 		!= SMCParse_Okay)
   876 	{
   877 		const char *err_string = textparsers->GetSMCErrorString(err);
   878 		if (!err_string)
   879 		{
   880 			err_string = "Unknown";
   881 		}
   882 
   883 		UTIL_Format(error, maxlength, "%s", err_string);
   884 
   885 		return false;
   886 	}
   887 
   888 	return true;
   889 }
   890 
   891 bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner)
   892 {
   893 	/* First sweep the categories owned by us */
   894 	CVector<unsigned int> obj_list;
   895 	for (size_t i = 0; i < m_Categories.size(); i++)
   896 	{
   897 		if (m_Categories[i]->obj->owner == owner)
   898 		{
   899 			obj_list.push_back(m_Categories[i]->obj->object_id);
   900 		}
   901 	}
   902 
   903 	for (size_t i = 0; i < obj_list.size(); i++)
   904 	{
   905 		RemoveFromMenu(obj_list[i]);
   906 	}
   907 
   908 	/* Now we can look for actual items */
   909 	for (size_t i = 0; i < m_Objects.size(); i++)
   910 	{
   911 		if (m_Objects[i]->is_free)
   912 		{
   913 			continue;
   914 		}
   915 		if (m_Objects[i]->owner == owner)
   916 		{
   917 			assert(m_Objects[i]->type != TopMenuObject_Category);
   918 			RemoveFromMenu(m_Objects[i]->object_id);
   919 		}
   920 	}
   921 
   922 	return true;
   923 }
   924 
   925 #define PARSE_STATE_NONE		0
   926 #define PARSE_STATE_MAIN		1
   927 #define PARSE_STATE_CATEGORY	2
   928 unsigned int ignore_parse_level = 0;
   929 unsigned int current_parse_state = 0;
   930 config_category_t *cur_cat = NULL;
   931 
   932 void TopMenu::ReadSMC_ParseStart()
   933 {
   934 	current_parse_state = PARSE_STATE_NONE;
   935 	ignore_parse_level = 0;
   936 	cur_cat = NULL;
   937 
   938 	/* Reset the old config */
   939 	m_Config.strings.Reset();
   940 	for (size_t i = 0; i < m_Config.cats.size(); i++)
   941 	{
   942 		delete m_Config.cats[i];
   943 	}
   944 	m_Config.cats.clear();
   945 }
   946 
   947 SMCParseResult TopMenu::ReadSMC_NewSection(const char *name, bool opt_quotes)
   948 {
   949 	if (ignore_parse_level)
   950 	{
   951 		ignore_parse_level++;
   952 	}
   953 	else
   954 	{
   955 		if (current_parse_state == PARSE_STATE_NONE)
   956 		{
   957 			if (strcmp(name, "Menu") == 0)
   958 			{
   959 				current_parse_state = PARSE_STATE_MAIN;
   960 			}
   961 			else
   962 			{
   963 				ignore_parse_level = 1;
   964 			}
   965 		}
   966 		else if (current_parse_state == PARSE_STATE_MAIN)
   967 		{
   968 			cur_cat = new config_category_t;
   969 			cur_cat->name = m_Config.strings.AddString(name);
   970 			m_Config.cats.push_back(cur_cat);
   971 			current_parse_state = PARSE_STATE_CATEGORY;
   972 		}
   973 		else
   974 		{
   975 			ignore_parse_level = 1;
   976 		}
   977 	}
   978 
   979 	return SMCParse_Continue;
   980 }
   981 
   982 SMCParseResult TopMenu::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
   983 {
   984 	if (ignore_parse_level > 0
   985 		|| current_parse_state != PARSE_STATE_CATEGORY
   986 		|| cur_cat == NULL)
   987 	{
   988 		return SMCParse_Continue;
   989 	}
   990 
   991 	if (strcmp(key, "item") == 0)
   992 	{
   993 		cur_cat->commands.push_back(m_Config.strings.AddString(value));
   994 	}
   995 
   996 	return SMCParse_Continue;
   997 }
   998 
   999 SMCParseResult TopMenu::ReadSMC_LeavingSection()
  1000 {
  1001 	if (ignore_parse_level)
  1002 	{
  1003 		ignore_parse_level--;
  1004 	}
  1005 	else
  1006 	{
  1007 		if (current_parse_state == PARSE_STATE_CATEGORY)
  1008 		{
  1009 			cur_cat = NULL;
  1010 			current_parse_state = PARSE_STATE_MAIN;
  1011 		}
  1012 		else if (current_parse_state == PARSE_STATE_MAIN)
  1013 		{
  1014 			current_parse_state = PARSE_STATE_NONE;
  1015 		}
  1016 	}
  1017 
  1018 	return SMCParse_Continue;
  1019 }
  1020 
  1021 unsigned int TopMenu::FindCategory(const char *name)
  1022 {
  1023 	topmenu_object_t **p_obj = m_ObjLookup.retrieve(name);
  1024 	if (!p_obj)
  1025 	{
  1026 		return 0;
  1027 	}
  1028 
  1029 	topmenu_object_t *obj = *p_obj;
  1030 	if (obj->type != TopMenuObject_Category)
  1031 	{
  1032 		return 0;
  1033 	}
  1034 
  1035 	return obj->object_id;
  1036 }
  1037 
  1038 int _SortObjectNamesDescending(const void *ptr1, const void *ptr2)
  1039 {
  1040 	obj_by_name_t *obj1 = (obj_by_name_t *)ptr1;
  1041 	obj_by_name_t *obj2 = (obj_by_name_t *)ptr2;
  1042 	return strcmp(obj1->name, obj2->name);
  1043 }
  1044 
  1045 unsigned int strncopy(char *dest, const char *src, size_t count)
  1046 {
  1047 	if (!count)
  1048 	{
  1049 		return 0;
  1050 	}
  1051 
  1052 	char *start = dest;
  1053 	while ((*src) && (--count))
  1054 	{
  1055 		*dest++ = *src++;
  1056 	}
  1057 	*dest = '\0';
  1058 
  1059 	return (dest - start);
  1060 }
  1061 
  1062 size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
  1063 {
  1064 	va_list ap;
  1065 	va_start(ap, fmt);
  1066 	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
  1067 	va_end(ap);
  1068 
  1069 	if (len >= maxlength)
  1070 	{
  1071 		buffer[maxlength - 1] = '\0';
  1072 		return (maxlength - 1);
  1073 	}
  1074 	else
  1075 	{
  1076 		return len;
  1077 	}
  1078 }