extensions/topmenus/TopMenu.cpp
changeset 1699 4368f4cc417b
parent 1656 b0b86d57530c
child 1766 d30c252089be
     1.1 --- a/extensions/topmenus/TopMenu.cpp	Mon Nov 19 23:11:10 2007 +0000
     1.2 +++ b/extensions/topmenus/TopMenu.cpp	Tue Nov 20 02:22:26 2007 +0000
     1.3 @@ -1,1105 +1,1119 @@
     1.4 -/**
     1.5 - * vim: set ts=4 :
     1.6 - * =============================================================================
     1.7 - * SourceMod Sample Extension
     1.8 - * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     1.9 - * =============================================================================
    1.10 - *
    1.11 - * This program is free software; you can redistribute it and/or modify it under
    1.12 - * the terms of the GNU General Public License, version 3.0, as published by the
    1.13 - * Free Software Foundation.
    1.14 - * 
    1.15 - * This program is distributed in the hope that it will be useful, but WITHOUT
    1.16 - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    1.17 - * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    1.18 - * details.
    1.19 - *
    1.20 - * You should have received a copy of the GNU General Public License along with
    1.21 - * this program.  If not, see <http://www.gnu.org/licenses/>.
    1.22 - *
    1.23 - * As a special exception, AlliedModders LLC gives you permission to link the
    1.24 - * code of this program (as well as its derivative works) to "Half-Life 2," the
    1.25 - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    1.26 - * by the Valve Corporation.  You must obey the GNU General Public License in
    1.27 - * all respects for all other code used.  Additionally, AlliedModders LLC grants
    1.28 - * this exception to all derivative works.  AlliedModders LLC defines further
    1.29 - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    1.30 - * or <http://www.sourcemod.net/license.php>.
    1.31 - *
    1.32 - * Version: $Id$
    1.33 - */
    1.34 -
    1.35 -#include <stdlib.h>
    1.36 -#include <stdarg.h>
    1.37 -#include "TopMenu.h"
    1.38 -
    1.39 -struct obj_by_name_t
    1.40 -{
    1.41 -	unsigned int obj_index;
    1.42 -	char name[64];
    1.43 -};
    1.44 -
    1.45 -int _SortObjectNamesDescending(const void *ptr1, const void *ptr2);
    1.46 -unsigned int strncopy(char *dest, const char *src, size_t count);
    1.47 -size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
    1.48 -
    1.49 -TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks)
    1.50 -{
    1.51 -	m_clients = NULL;
    1.52 -	m_SerialNo = 1;
    1.53 -	m_pTitle = callbacks;
    1.54 -	m_max_clients = 0;
    1.55 -
    1.56 -	if (playerhelpers->IsServerActivated())
    1.57 -	{
    1.58 -		CreatePlayers(playerhelpers->GetMaxClients());
    1.59 -	}
    1.60 -}
    1.61 -
    1.62 -TopMenu::~TopMenu()
    1.63 -{
    1.64 -	/* Delete all categories */
    1.65 -	while (m_Categories.size())
    1.66 -	{
    1.67 -		RemoveFromMenu(m_Categories[0]->obj->object_id);
    1.68 -	}
    1.69 -
    1.70 -	/* Remove all objects */
    1.71 -	for (size_t i = 0; i < m_Objects.size(); i++)
    1.72 -	{
    1.73 -		assert(m_Objects[i]->is_free == true);
    1.74 -		delete m_Objects[i];
    1.75 -	}
    1.76 -
    1.77 -	m_pTitle->OnTopMenuObjectRemoved(this, 0);
    1.78 -
    1.79 -	/* Delete all cached config entries */
    1.80 -	for (size_t i = 0; i < m_Config.cats.size(); i++)
    1.81 -	{
    1.82 -		delete m_Config.cats[i];
    1.83 -	}
    1.84 -
    1.85 -	/* Sweep players */
    1.86 -	for (size_t i = 0; i <= (size_t)m_max_clients; i++)
    1.87 -	{
    1.88 -		TearDownClient(&m_clients[i]);
    1.89 -	}
    1.90 -	delete [] m_clients;
    1.91 -}
    1.92 -
    1.93 -void TopMenu::OnClientConnected(int client)
    1.94 -{
    1.95 -	if (m_clients == NULL)
    1.96 -	{
    1.97 -		return;
    1.98 -	}
    1.99 -
   1.100 -	topmenu_player_t *player = &m_clients[client];
   1.101 -	TearDownClient(player);
   1.102 -}
   1.103 -
   1.104 -void TopMenu::OnClientDisconnected(int client)
   1.105 -{
   1.106 -	if (m_clients == NULL)
   1.107 -	{
   1.108 -		return;
   1.109 -	}
   1.110 -
   1.111 -	topmenu_player_t *player = &m_clients[client];
   1.112 -	TearDownClient(player);
   1.113 -}
   1.114 -
   1.115 -void TopMenu::OnServerActivated(int max_clients)
   1.116 -{
   1.117 -	if (m_clients == NULL)
   1.118 -	{
   1.119 -		CreatePlayers(max_clients);
   1.120 -	}
   1.121 -}
   1.122 -
   1.123 -unsigned int TopMenu::AddToMenu(const char *name,
   1.124 -								TopMenuObjectType type,
   1.125 -								ITopMenuObjectCallbacks *callbacks,
   1.126 -								IdentityToken_t *owner,
   1.127 -								const char *cmdname,
   1.128 -								FlagBits flags,
   1.129 -								unsigned int parent)
   1.130 -{
   1.131 -	return AddToMenu2(name, type, callbacks, owner, cmdname, flags, parent, NULL);
   1.132 -}
   1.133 -
   1.134 -unsigned int TopMenu::AddToMenu2(const char *name,
   1.135 -								 TopMenuObjectType type,
   1.136 -								 ITopMenuObjectCallbacks *callbacks,
   1.137 -								 IdentityToken_t *owner,
   1.138 -								 const char *cmdname,
   1.139 -								 FlagBits flags,
   1.140 -								 unsigned int parent,
   1.141 -								 const char *info_string)
   1.142 -{
   1.143 -	/* Sanity checks */
   1.144 -	if (type == TopMenuObject_Category && parent != 0)
   1.145 -	{
   1.146 -		return 0;
   1.147 -	}
   1.148 -	else if (type == TopMenuObject_Item && parent == 0)
   1.149 -	{
   1.150 -		return 0;
   1.151 -	}
   1.152 -	else if (m_ObjLookup.retrieve(name) != NULL)
   1.153 -	{
   1.154 -		return 0;
   1.155 -	}
   1.156 -	else if (type != TopMenuObject_Item && type != TopMenuObject_Category)
   1.157 -	{
   1.158 -		return 0;
   1.159 -	}
   1.160 -
   1.161 -	/* If we're adding an item, make sure the parent is valid, 
   1.162 -	 * and that the parent is a category.
   1.163 -	 */
   1.164 -	topmenu_object_t *parent_obj = NULL;
   1.165 -	topmenu_category_t *parent_cat = NULL;
   1.166 -	if (type == TopMenuObject_Item)
   1.167 -	{
   1.168 -		/* Check parent index.  Note it will be >= 1 here. */
   1.169 -		if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free)
   1.170 -		{
   1.171 -			return 0;
   1.172 -		}
   1.173 -		parent_obj = m_Objects[parent - 1];
   1.174 -
   1.175 -		/* Find an equivalent pointer in the category array. */
   1.176 -		for (size_t i = 0; i < m_Categories.size(); i++)
   1.177 -		{
   1.178 -			if (m_Categories[i]->obj == parent_obj)
   1.179 -			{
   1.180 -				parent_cat = m_Categories[i];
   1.181 -				break;
   1.182 -			}
   1.183 -		}
   1.184 -
   1.185 -		/* If none was found, leave. */
   1.186 -		if (parent_cat == NULL)
   1.187 -		{
   1.188 -			return 0;
   1.189 -		}
   1.190 -	}
   1.191 -
   1.192 -	/* Re-use an old object pointer if we can. */
   1.193 -	topmenu_object_t *obj = NULL;
   1.194 -	for (size_t i = 0; i < m_Objects.size(); i++)
   1.195 -	{
   1.196 -		if (m_Objects[i]->is_free == true)
   1.197 -		{
   1.198 -			obj = m_Objects[i];
   1.199 -			break;
   1.200 -		}
   1.201 -	}
   1.202 -
   1.203 -	/* Otherwise, allocate a new one. */
   1.204 -	if (obj == NULL)
   1.205 -	{
   1.206 -		obj = new topmenu_object_t;
   1.207 -		obj->object_id = ((unsigned int)m_Objects.size()) + 1;
   1.208 -		m_Objects.push_back(obj);
   1.209 -	}
   1.210 -
   1.211 -	/* Initialize the object's properties. */
   1.212 -	obj->callbacks = callbacks;
   1.213 -	obj->flags = flags;
   1.214 -	obj->owner = owner;
   1.215 -	obj->type = type;
   1.216 -	obj->is_free = false;
   1.217 -	obj->parent = parent_obj;
   1.218 -	strncopy(obj->name, name, sizeof(obj->name));
   1.219 -	strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname));
   1.220 -	strncopy(obj->info, info_string ? info_string : "", sizeof(obj->info));
   1.221 -
   1.222 -	if (obj->type == TopMenuObject_Category)
   1.223 -	{
   1.224 -		/* Create a new category entry */
   1.225 -		topmenu_category_t *cat = new topmenu_category_t;
   1.226 -		cat->obj = obj;
   1.227 -		cat->reorder = false;
   1.228 -		cat->serial = 1;
   1.229 -
   1.230 -		/* Add it, then update our serial change number. */
   1.231 -		m_Categories.push_back(cat);
   1.232 -		m_SerialNo++;
   1.233 -
   1.234 -		/* Updating sorting info */
   1.235 -		m_bCatsNeedResort = true;
   1.236 -	}
   1.237 -	else if (obj->type == TopMenuObject_Item)
   1.238 -	{
   1.239 -		/* Update the category, mark it as needing changes */
   1.240 -		parent_cat->obj_list.push_back(obj);
   1.241 -		parent_cat->reorder = true;
   1.242 -		parent_cat->serial++;
   1.243 -
   1.244 -		/* If the category just went from 0 to 1 items, mark it as 
   1.245 -		 * changed, so clients get the category drawn.
   1.246 -		 */
   1.247 -		if (parent_cat->obj_list.size() == 1)
   1.248 -		{
   1.249 -			m_SerialNo++;
   1.250 -		}
   1.251 -	}
   1.252 -
   1.253 -	m_ObjLookup.insert(name, obj);
   1.254 -
   1.255 -	return obj->object_id;
   1.256 -}
   1.257 -
   1.258 -const char *TopMenu::GetObjectInfoString(unsigned int object_id)
   1.259 -{
   1.260 -	if (object_id == 0 
   1.261 -		|| object_id > m_Objects.size() 
   1.262 -		|| m_Objects[object_id - 1]->is_free)
   1.263 -	{
   1.264 -		return NULL;
   1.265 -	}
   1.266 -
   1.267 -	topmenu_object_t *obj = m_Objects[object_id - 1];
   1.268 -
   1.269 -	return obj->info;
   1.270 -}
   1.271 -
   1.272 -void TopMenu::RemoveFromMenu(unsigned int object_id)
   1.273 -{
   1.274 -	if (object_id == 0 
   1.275 -		|| object_id > m_Objects.size() 
   1.276 -		|| m_Objects[object_id - 1]->is_free)
   1.277 -	{
   1.278 -		return;
   1.279 -	}
   1.280 -
   1.281 -	topmenu_object_t *obj = m_Objects[object_id - 1];
   1.282 -
   1.283 -	m_ObjLookup.remove(obj->name);
   1.284 -
   1.285 -	if (obj->type == TopMenuObject_Category)
   1.286 -	{
   1.287 -		/* Find it in the category list. */
   1.288 -		for (size_t i = 0; i < m_Categories.size(); i++)
   1.289 -		{
   1.290 -			if (m_Categories[i]->obj == obj)
   1.291 -			{
   1.292 -				/* Mark all children as removed + free.  Note we could
   1.293 -				 * call into RemoveMenuItem() for this, but it'd be very 
   1.294 -				 * inefficient!
   1.295 -				 */
   1.296 -				topmenu_category_t *cat = m_Categories[i];
   1.297 -				for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++)
   1.298 -				{
   1.299 -					cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id);
   1.300 -					cat->obj_list[j]->is_free = true;
   1.301 -				}
   1.302 -				
   1.303 -				/* Remove the category from the list, then delete it. */
   1.304 -				m_Categories.erase(m_Categories.iterAt(i));
   1.305 -				delete cat;
   1.306 -				break;
   1.307 -			}
   1.308 -		}
   1.309 -
   1.310 -		/* Update the root as changed. */
   1.311 -		m_SerialNo++;
   1.312 -		m_bCatsNeedResort = true;
   1.313 -	}
   1.314 -	else if (obj->type == TopMenuObject_Item)
   1.315 -	{
   1.316 -		/* Find the category this item is in. */
   1.317 -		topmenu_category_t *parent_cat = NULL;
   1.318 -		for (size_t i = 0; i < m_Categories.size(); i++)
   1.319 -		{
   1.320 -			if (m_Categories[i]->obj == obj->parent)
   1.321 -			{
   1.322 -				parent_cat = m_Categories[i];
   1.323 -				break;
   1.324 -			}
   1.325 -		}
   1.326 -
   1.327 -		/* Erase it from the category's lists. */
   1.328 -		if (parent_cat)
   1.329 -		{
   1.330 -			for (size_t i = 0; i < parent_cat->obj_list.size(); i++)
   1.331 -			{
   1.332 -				if (parent_cat->obj_list[i] == obj)
   1.333 -				{
   1.334 -					parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i));
   1.335 -
   1.336 -					/* If this category now has no items, mark root as changed 
   1.337 -					 * so clients won't get the category drawn anymore.
   1.338 -					 */
   1.339 -					if (parent_cat->obj_list.size() == 0)
   1.340 -					{
   1.341 -						m_SerialNo++;
   1.342 -					}
   1.343 -					break;
   1.344 -				}
   1.345 -			}
   1.346 -
   1.347 -			/* Update the category as changed. */
   1.348 -			parent_cat->reorder = true;
   1.349 -			parent_cat->serial++;
   1.350 -		}
   1.351 -	}
   1.352 -
   1.353 -	/* The callbacks pointer is still valid, so fire away! */
   1.354 -	obj->callbacks->OnTopMenuObjectRemoved(this, object_id);
   1.355 -
   1.356 -	/* Finally, mark the object as free. */
   1.357 -	obj->is_free = true;
   1.358 -}
   1.359 -
   1.360 -bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position)
   1.361 -{
   1.362 -	if (m_clients == NULL)
   1.363 -	{
   1.364 -		return false;
   1.365 -	}
   1.366 -
   1.367 -	IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
   1.368 -	if (!pPlayer->IsInGame())
   1.369 -	{
   1.370 -		return false;
   1.371 -	}
   1.372 -
   1.373 -	UpdateClientRoot(client, pPlayer);
   1.374 -
   1.375 -	topmenu_player_t *pClient = &m_clients[client];
   1.376 -	if (pClient->root == NULL)
   1.377 -	{
   1.378 -		return false;
   1.379 -	}
   1.380 -
   1.381 -	bool return_value = false;
   1.382 -
   1.383 -	if (position == TopMenuPosition_LastCategory && 
   1.384 -		pClient->last_category < m_Categories.size())
   1.385 -	{
   1.386 -		return_value = DisplayCategory(client, pClient->last_category, hold_time, true);
   1.387 -		if (!return_value)
   1.388 -		{
   1.389 -			return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
   1.390 -		}
   1.391 -	}
   1.392 -	else if (position == TopMenuPosition_LastRoot)
   1.393 -	{
   1.394 -		pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
   1.395 -	}
   1.396 -	else if (position == TopMenuPosition_Start)
   1.397 -	{
   1.398 -		pClient->last_position = 0;
   1.399 -		pClient->last_category = 0;
   1.400 -		return_value = pClient->root->Display(client, hold_time);
   1.401 -	}
   1.402 -
   1.403 -	return return_value;
   1.404 -}
   1.405 -
   1.406 -bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position)
   1.407 -{
   1.408 -	UpdateClientCategory(client, category);
   1.409 -
   1.410 -	topmenu_player_t *pClient = &m_clients[client];
   1.411 -	if (category >= pClient->cat_count
   1.412 -		|| pClient->cats[category].menu == NULL)
   1.413 -	{
   1.414 -		return false;
   1.415 -	}
   1.416 -
   1.417 -	bool return_value = false;
   1.418 -
   1.419 -	topmenu_player_category_t *player_cat = &(pClient->cats[category]);
   1.420 -
   1.421 -	pClient->last_category = category;
   1.422 -	if (last_position)
   1.423 -	{
   1.424 -		return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position);
   1.425 -	}
   1.426 -	else
   1.427 -	{
   1.428 -		return_value = player_cat->menu->Display(client, hold_time);
   1.429 -	}
   1.430 -
   1.431 -	return return_value;
   1.432 -}
   1.433 -
   1.434 -void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
   1.435 -{
   1.436 -	const char *item_name = menu->GetItemInfo(item, NULL);
   1.437 -	if (!item_name)
   1.438 -	{
   1.439 -		return;
   1.440 -	}
   1.441 -
   1.442 -	topmenu_object_t *obj;
   1.443 -	topmenu_player_t *pClient = &m_clients[client];
   1.444 -	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
   1.445 -	if (pObject == NULL)
   1.446 -	{
   1.447 -		return;
   1.448 -	}
   1.449 -
   1.450 -	obj = *pObject;
   1.451 -
   1.452 -	/* We now have the object... what do we do with it? */
   1.453 -	if (obj->type == TopMenuObject_Category)
   1.454 -	{
   1.455 -		/* If it's a category, the user wants to view it.. */
   1.456 -		for (size_t i = 0; i < m_Categories.size(); i++)
   1.457 -		{
   1.458 -			if (m_Categories[i]->obj == obj)
   1.459 -			{
   1.460 -				pClient->last_root_pos = item_on_page;
   1.461 -				if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false))
   1.462 -				{
   1.463 -					/* If we can't display the category, re-display the root menu */
   1.464 -					DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot);
   1.465 -				}
   1.466 -				break;
   1.467 -			}
   1.468 -		}
   1.469 -	}
   1.470 -	else
   1.471 -	{
   1.472 -		pClient->last_position = item_on_page;
   1.473 -		
   1.474 -		/* Re-check access in case this user had their credentials revoked */
   1.475 -		if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
   1.476 -		{
   1.477 -			DisplayMenu(client, 0, TopMenuPosition_LastCategory);
   1.478 -			return;
   1.479 -		}
   1.480 -		
   1.481 -		/* Pass the information on to the callback */
   1.482 -		obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id);
   1.483 -	}
   1.484 -}
   1.485 -
   1.486 -void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
   1.487 -{
   1.488 -	const char *item_name = menu->GetItemInfo(item, NULL);
   1.489 -	if (!item_name)
   1.490 -	{
   1.491 -		return;
   1.492 -	}
   1.493 -
   1.494 -	topmenu_object_t *obj;
   1.495 -	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
   1.496 -	if (pObject == NULL)
   1.497 -	{
   1.498 -		return;
   1.499 -	}
   1.500 -
   1.501 -	obj = *pObject;
   1.502 -
   1.503 -	style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id);
   1.504 -	if (style != ITEMDRAW_DEFAULT)
   1.505 -	{
   1.506 -		return;
   1.507 -	}
   1.508 -
   1.509 -	if (obj->cmdname[0] == '\0')
   1.510 -	{
   1.511 -		return;
   1.512 -	}
   1.513 -
   1.514 -	if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
   1.515 -	{
   1.516 -		style = ITEMDRAW_IGNORE;
   1.517 -	}
   1.518 -}
   1.519 -
   1.520 -unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu,
   1.521 -								int client,
   1.522 -								IMenuPanel *panel,
   1.523 -								unsigned int item,
   1.524 -								const ItemDrawInfo &dr)
   1.525 -{
   1.526 -	const char *item_name = menu->GetItemInfo(item, NULL);
   1.527 -	if (!item_name)
   1.528 -	{
   1.529 -		return 0;
   1.530 -	}
   1.531 -
   1.532 -	topmenu_object_t *obj;
   1.533 -	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
   1.534 -	if (pObject == NULL)
   1.535 -	{
   1.536 -		return 0;
   1.537 -	}
   1.538 -
   1.539 -	obj = *pObject;
   1.540 -
   1.541 -	/* Ask the object to render the text for this client */
   1.542 -	char renderbuf[64];
   1.543 -	obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf));
   1.544 -
   1.545 -	/* Build the new draw info */
   1.546 -	ItemDrawInfo new_dr = dr;
   1.547 -	new_dr.display = renderbuf;
   1.548 -
   1.549 -	/* Man I love the menu API.  Ask the panel to draw the item and give the position 
   1.550 -	 * back to Core's renderer.  This way we don't have to worry about keeping the 
   1.551 -	 * render buffer static!
   1.552 -	 */
   1.553 -	return panel->DrawItem(new_dr);
   1.554 -}
   1.555 -
   1.556 -void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
   1.557 -{
   1.558 -	if (reason == MenuCancel_ExitBack)
   1.559 -	{
   1.560 -		/* If the client chose exit back, they were on a category menu, and we can
   1.561 -		 * now display the root menu from the last known position.
   1.562 -		 */
   1.563 -		DisplayMenu(client, 0, TopMenuPosition_LastRoot);
   1.564 -	}
   1.565 -}
   1.566 -
   1.567 -void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer)
   1.568 -{
   1.569 -	topmenu_player_t *pClient = &m_clients[client];
   1.570 -	IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client);
   1.571 -
   1.572 -	/* Determine if an update is necessary */
   1.573 -	bool is_update_needed = false;
   1.574 -	if (pClient->menu_serial != m_SerialNo)
   1.575 -	{
   1.576 -		is_update_needed = true;
   1.577 -	}
   1.578 -	else if (pPlayer->GetUserId() != pClient->user_id)
   1.579 -	{
   1.580 -		is_update_needed = true;
   1.581 -	}
   1.582 -
   1.583 -	/* If no update is needed at the root level, just leave now */
   1.584 -	if (!is_update_needed)
   1.585 -	{
   1.586 -		return;
   1.587 -	}
   1.588 -
   1.589 -	/* First we need to flush the cache... */
   1.590 -	TearDownClient(pClient);
   1.591 -
   1.592 -	/* Now, rebuild the category list, but don't create menus */
   1.593 -	if (m_Categories.size() == 0)
   1.594 -	{
   1.595 -		pClient->cat_count = 0;
   1.596 -		pClient->cats = NULL;
   1.597 -	}
   1.598 -	else
   1.599 -	{
   1.600 -		pClient->cat_count = (unsigned int)m_Categories.size();
   1.601 -		pClient->cats = new topmenu_player_category_t[pClient->cat_count];
   1.602 -		memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count);
   1.603 -	}
   1.604 -
   1.605 -	/* Re-sort the root categories if needed */
   1.606 -	SortCategoriesIfNeeded();
   1.607 -
   1.608 -	/* Build the root menu */
   1.609 -	IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
   1.610 -
   1.611 -	/* Add the sorted items */
   1.612 -	for (size_t i = 0; i < m_SortedCats.size(); i++)
   1.613 -	{
   1.614 -		if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0)
   1.615 -		{
   1.616 -			continue;
   1.617 -		}
   1.618 -		root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo(""));
   1.619 -	}
   1.620 -
   1.621 -	/* Now we need to handle un-sorted items.  This is slightly trickier, as we need to 
   1.622 -	 * pre-render each category name, and cache those names.  Phew!
   1.623 -	 */
   1.624 -	if (m_UnsortedCats.size())
   1.625 -	{
   1.626 -		obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()];
   1.627 -		for (size_t i = 0; i < m_UnsortedCats.size(); i++)
   1.628 -		{
   1.629 -			obj_by_name_t *temp_obj = &item_list[i];
   1.630 -			topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj;
   1.631 -			obj->callbacks->OnTopMenuDisplayOption(this,
   1.632 -				client, 
   1.633 -				obj->object_id,
   1.634 -				temp_obj->name,
   1.635 -				sizeof(temp_obj->name));
   1.636 -			temp_obj->obj_index = m_UnsortedCats[i];
   1.637 -		}
   1.638 -
   1.639 -		/* Sort our temp list */
   1.640 -		qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
   1.641 -
   1.642 -		/* Add the new sorted categories */
   1.643 -		for (size_t i = 0; i < m_UnsortedCats.size(); i++)
   1.644 -		{
   1.645 -			if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0)
   1.646 -			{
   1.647 -				continue;
   1.648 -			}
   1.649 -			root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo(""));
   1.650 -		}
   1.651 -
   1.652 -		delete [] item_list;
   1.653 -	}
   1.654 -
   1.655 -	/* Set the menu's title */
   1.656 -	char renderbuf[128];
   1.657 -	m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf));
   1.658 -	root_menu->SetDefaultTitle(renderbuf);
   1.659 -
   1.660 -	/* The client is now fully updated */
   1.661 -	pClient->root = root_menu;
   1.662 -	pClient->user_id = pPlayer->GetUserId();
   1.663 -	pClient->menu_serial = m_SerialNo;
   1.664 -	pClient->last_position = 0;
   1.665 -	pClient->last_category = 0;
   1.666 -	pClient->last_root_pos = 0;
   1.667 -}
   1.668 -
   1.669 -void TopMenu::UpdateClientCategory(int client, unsigned int category)
   1.670 -{
   1.671 -	/* Update the client's root menu just in case it needs it.  This 
   1.672 -	 * will validate that we have both a valid client and a valid
   1.673 -	 * category structure for that client.
   1.674 -	 */
   1.675 -	UpdateClientRoot(client);
   1.676 -
   1.677 -	/* Now it's guaranteed that our category tables will be usable */
   1.678 -	topmenu_player_t *pClient = &m_clients[client];
   1.679 -	topmenu_category_t *cat = m_Categories[category];
   1.680 -	topmenu_player_category_t *player_cat = &(pClient->cats[category]);
   1.681 -
   1.682 -	/* Does the category actually need updating? */
   1.683 -	if (player_cat->serial == cat->serial)
   1.684 -	{
   1.685 -		return;
   1.686 -	}
   1.687 -
   1.688 -	/* Destroy any existing menu */
   1.689 -	if (player_cat->menu)
   1.690 -	{
   1.691 -		player_cat->menu->Destroy();
   1.692 -		player_cat->menu = NULL;
   1.693 -	}
   1.694 -
   1.695 -	if (pClient->last_category == category)
   1.696 -	{
   1.697 -		pClient->last_position = 0;
   1.698 -	}
   1.699 -
   1.700 -	IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
   1.701 -	
   1.702 -	/* Categories get an "exit back" button */
   1.703 -	cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK);
   1.704 -
   1.705 -	/* Re-sort the category if needed */
   1.706 -	SortCategoryIfNeeded(category);
   1.707 -
   1.708 -	/* Build the menu with the sorted items first */
   1.709 -	for (size_t i = 0; i < cat->sorted.size(); i++)
   1.710 -	{
   1.711 -		cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo(""));
   1.712 -	}
   1.713 -
   1.714 -	/* Now handle unsorted items */
   1.715 -	if (cat->unsorted.size())
   1.716 -	{
   1.717 -		/* Build a list of the item names */
   1.718 -		obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()];
   1.719 -		for (size_t i = 0; i < cat->unsorted.size(); i++)
   1.720 -		{
   1.721 -			obj_by_name_t *item = &item_list[i];
   1.722 -			topmenu_object_t *obj = cat->unsorted[i];
   1.723 -			obj->callbacks->OnTopMenuDisplayOption(this,
   1.724 -				client,
   1.725 -				obj->object_id,
   1.726 -				item->name,
   1.727 -				sizeof(item->name));
   1.728 -			item->obj_index = (unsigned int)i;
   1.729 -		}
   1.730 -
   1.731 -		/* Sort the names */
   1.732 -		qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
   1.733 -
   1.734 -		/* Add to the menu */
   1.735 -		for (size_t i = 0; i < cat->unsorted.size(); i++)
   1.736 -		{
   1.737 -			cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo(""));
   1.738 -		}
   1.739 -
   1.740 -		delete [] item_list;
   1.741 -	}
   1.742 -
   1.743 -	/* Set the menu's title */
   1.744 -	char renderbuf[128];
   1.745 -	cat->obj->callbacks->OnTopMenuDisplayTitle(this, 
   1.746 -		client, 
   1.747 -		cat->obj->object_id, 
   1.748 -		renderbuf, 
   1.749 -		sizeof(renderbuf));
   1.750 -	cat_menu->SetDefaultTitle(renderbuf);
   1.751 -
   1.752 -	/* We're done! */
   1.753 -	player_cat->menu = cat_menu;
   1.754 -	player_cat->serial = cat->serial;
   1.755 -}
   1.756 -
   1.757 -void TopMenu::SortCategoryIfNeeded(unsigned int category)
   1.758 -{
   1.759 -	topmenu_category_t *cat = m_Categories[category];
   1.760 -	if (!cat->reorder)
   1.761 -	{
   1.762 -		return;
   1.763 -	}
   1.764 -
   1.765 -	cat->sorted.clear();
   1.766 -	cat->unsorted.clear();
   1.767 -
   1.768 -	if (cat->obj_list.size() == 0)
   1.769 -	{
   1.770 -		cat->reorder = false;
   1.771 -		return;
   1.772 -	}
   1.773 -
   1.774 -	CVector<unsigned int> to_sort;
   1.775 -	for (size_t i = 0; i < cat->obj_list.size(); i++)
   1.776 -	{
   1.777 -		to_sort.push_back(i);
   1.778 -	}
   1.779 -
   1.780 -	/* Find a matching category in the configs */
   1.781 -	config_category_t *config_cat = NULL; 
   1.782 -	for (size_t i = 0; i < m_Config.cats.size(); i++)
   1.783 -	{
   1.784 -		if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0)
   1.785 -		{
   1.786 -			config_cat = m_Config.cats[i];
   1.787 -			break;
   1.788 -		}
   1.789 -	}
   1.790 -
   1.791 -	/* If there is a matching category, build a pre-sorted item list */
   1.792 -	if (config_cat != NULL)
   1.793 -	{
   1.794 -		/* Find matching objects in this category */
   1.795 -		for (size_t i = 0; i < config_cat->commands.size(); i++)
   1.796 -		{
   1.797 -			const char *config_name = m_Config.strings.GetString(config_cat->commands[i]);
   1.798 -			for (size_t j = 0; j < to_sort.size(); j++)
   1.799 -			{
   1.800 -				if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0)
   1.801 -				{
   1.802 -					/* Place in the final list, then remove from the temporary list */
   1.803 -					cat->sorted.push_back(cat->obj_list[to_sort[j]]);
   1.804 -					to_sort.erase(to_sort.iterAt(j));
   1.805 -					break;
   1.806 -				}
   1.807 -			}
   1.808 -		}
   1.809 -	}
   1.810 -
   1.811 -	/* Push any remaining items onto the unsorted list */
   1.812 -	for (size_t i = 0; i < to_sort.size(); i++)
   1.813 -	{
   1.814 -		cat->unsorted.push_back(cat->obj_list[to_sort[i]]);
   1.815 -	}
   1.816 -
   1.817 -	cat->reorder = false;
   1.818 -}
   1.819 -
   1.820 -void TopMenu::SortCategoriesIfNeeded()
   1.821 -{
   1.822 -	if (!m_bCatsNeedResort)
   1.823 -	{
   1.824 -		return;
   1.825 -	}
   1.826 -
   1.827 -	/* Clear sort results */
   1.828 -	m_SortedCats.clear();
   1.829 -	m_UnsortedCats.clear();
   1.830 -
   1.831 -	if (m_Categories.size() == 0)
   1.832 -	{
   1.833 -		m_bCatsNeedResort = false;
   1.834 -		return;
   1.835 -	}
   1.836 -
   1.837 -	CVector<unsigned int> to_sort;
   1.838 -	for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++)
   1.839 -	{
   1.840 -		to_sort.push_back(i);
   1.841 -	}
   1.842 -
   1.843 -	/* If we have any predefined categories, add them in as they appear. */
   1.844 -	for (size_t i= 0; i < m_Config.cats.size(); i++)
   1.845 -	{
   1.846 -		/* Find this category and map it in if we can */
   1.847 -		for (size_t j = 0; j < to_sort.size(); j++)
   1.848 -		{
   1.849 -			if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), 
   1.850 -					   m_Categories[to_sort[j]]->obj->name) == 0)
   1.851 -			{
   1.852 -				/* Add to the real list and remove from the temporary */
   1.853 -				m_SortedCats.push_back(to_sort[j]);
   1.854 -				to_sort.erase(to_sort.iterAt(j));
   1.855 -				break;
   1.856 -			}
   1.857 -		}
   1.858 -	}
   1.859 -
   1.860 -	/* Push any remaining items onto the unsorted list */
   1.861 -	for (size_t i = 0; i < to_sort.size(); i++)
   1.862 -	{
   1.863 -		m_UnsortedCats.push_back(to_sort[i]);
   1.864 -	}
   1.865 -
   1.866 -	m_bCatsNeedResort = false;
   1.867 -}
   1.868 -
   1.869 -void TopMenu::CreatePlayers(int max_clients)
   1.870 -{
   1.871 -	m_max_clients = max_clients;
   1.872 -	m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1));
   1.873 -	memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1));
   1.874 -}
   1.875 -
   1.876 -void TopMenu::TearDownClient(topmenu_player_t *player)
   1.877 -{
   1.878 -	if (player->cats != NULL)
   1.879 -	{
   1.880 -		for (unsigned int i = 0; i < player->cat_count; i++)
   1.881 -		{
   1.882 -			topmenu_player_category_t *player_cat = &(player->cats[i]);
   1.883 -			if (player_cat->menu != NULL)
   1.884 -			{
   1.885 -				player_cat->menu->Destroy();
   1.886 -			}
   1.887 -		}
   1.888 -		delete [] player->cats;
   1.889 -	}
   1.890 -
   1.891 -	if (player->root != NULL)
   1.892 -	{
   1.893 -		player->root->Destroy();
   1.894 -	}
   1.895 -
   1.896 -	memset(player, 0, sizeof(topmenu_player_t));
   1.897 -}
   1.898 -
   1.899 -bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength)
   1.900 -{
   1.901 -	SMCError err;
   1.902 -	SMCStates states;
   1.903 -
   1.904 -	if ((err = textparsers->ParseFile_SMC(file, this, &states))
   1.905 -		!= SMCError_Okay)
   1.906 -	{
   1.907 -		const char *err_string = textparsers->GetSMCErrorString(err);
   1.908 -		if (!err_string)
   1.909 -		{
   1.910 -			err_string = "Unknown";
   1.911 -		}
   1.912 -
   1.913 -		UTIL_Format(error, maxlength, "%s", err_string);
   1.914 -
   1.915 -		return false;
   1.916 -	}
   1.917 -
   1.918 -	return true;
   1.919 -}
   1.920 -
   1.921 -bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner)
   1.922 -{
   1.923 -	/* First sweep the categories owned by us */
   1.924 -	CVector<unsigned int> obj_list;
   1.925 -	for (size_t i = 0; i < m_Categories.size(); i++)
   1.926 -	{
   1.927 -		if (m_Categories[i]->obj->owner == owner)
   1.928 -		{
   1.929 -			obj_list.push_back(m_Categories[i]->obj->object_id);
   1.930 -		}
   1.931 -	}
   1.932 -
   1.933 -	for (size_t i = 0; i < obj_list.size(); i++)
   1.934 -	{
   1.935 -		RemoveFromMenu(obj_list[i]);
   1.936 -	}
   1.937 -
   1.938 -	/* Now we can look for actual items */
   1.939 -	for (size_t i = 0; i < m_Objects.size(); i++)
   1.940 -	{
   1.941 -		if (m_Objects[i]->is_free)
   1.942 -		{
   1.943 -			continue;
   1.944 -		}
   1.945 -		if (m_Objects[i]->owner == owner)
   1.946 -		{
   1.947 -			assert(m_Objects[i]->type != TopMenuObject_Category);
   1.948 -			RemoveFromMenu(m_Objects[i]->object_id);
   1.949 -		}
   1.950 -	}
   1.951 -
   1.952 -	return true;
   1.953 -}
   1.954 -
   1.955 -#define PARSE_STATE_NONE		0
   1.956 -#define PARSE_STATE_MAIN		1
   1.957 -#define PARSE_STATE_CATEGORY	2
   1.958 -unsigned int ignore_parse_level = 0;
   1.959 -unsigned int current_parse_state = 0;
   1.960 -config_category_t *cur_cat = NULL;
   1.961 -
   1.962 -void TopMenu::ReadSMC_ParseStart()
   1.963 -{
   1.964 -	current_parse_state = PARSE_STATE_NONE;
   1.965 -	ignore_parse_level = 0;
   1.966 -	cur_cat = NULL;
   1.967 -
   1.968 -	/* Reset the old config */
   1.969 -	m_Config.strings.Reset();
   1.970 -	for (size_t i = 0; i < m_Config.cats.size(); i++)
   1.971 -	{
   1.972 -		delete m_Config.cats[i];
   1.973 -	}
   1.974 -	m_Config.cats.clear();
   1.975 -}
   1.976 -
   1.977 -SMCResult TopMenu::ReadSMC_NewSection(const SMCStates *states, const char *name)
   1.978 -{
   1.979 -	if (ignore_parse_level)
   1.980 -	{
   1.981 -		ignore_parse_level++;
   1.982 -	}
   1.983 -	else
   1.984 -	{
   1.985 -		if (current_parse_state == PARSE_STATE_NONE)
   1.986 -		{
   1.987 -			if (strcmp(name, "Menu") == 0)
   1.988 -			{
   1.989 -				current_parse_state = PARSE_STATE_MAIN;
   1.990 -			}
   1.991 -			else
   1.992 -			{
   1.993 -				ignore_parse_level = 1;
   1.994 -			}
   1.995 -		}
   1.996 -		else if (current_parse_state == PARSE_STATE_MAIN)
   1.997 -		{
   1.998 -			cur_cat = new config_category_t;
   1.999 -			cur_cat->name = m_Config.strings.AddString(name);
  1.1000 -			m_Config.cats.push_back(cur_cat);
  1.1001 -			current_parse_state = PARSE_STATE_CATEGORY;
  1.1002 -		}
  1.1003 -		else
  1.1004 -		{
  1.1005 -			ignore_parse_level = 1;
  1.1006 -		}
  1.1007 -	}
  1.1008 -
  1.1009 -	return SMCResult_Continue;
  1.1010 -}
  1.1011 -
  1.1012 -SMCResult TopMenu::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
  1.1013 -{
  1.1014 -	if (ignore_parse_level > 0
  1.1015 -		|| current_parse_state != PARSE_STATE_CATEGORY
  1.1016 -		|| cur_cat == NULL)
  1.1017 -	{
  1.1018 -		return SMCResult_Continue;
  1.1019 -	}
  1.1020 -
  1.1021 -	if (strcmp(key, "item") == 0)
  1.1022 -	{
  1.1023 -		cur_cat->commands.push_back(m_Config.strings.AddString(value));
  1.1024 -	}
  1.1025 -
  1.1026 -	return SMCResult_Continue;
  1.1027 -}
  1.1028 -
  1.1029 -SMCResult TopMenu::ReadSMC_LeavingSection(const SMCStates *states)
  1.1030 -{
  1.1031 -	if (ignore_parse_level)
  1.1032 -	{
  1.1033 -		ignore_parse_level--;
  1.1034 -	}
  1.1035 -	else
  1.1036 -	{
  1.1037 -		if (current_parse_state == PARSE_STATE_CATEGORY)
  1.1038 -		{
  1.1039 -			cur_cat = NULL;
  1.1040 -			current_parse_state = PARSE_STATE_MAIN;
  1.1041 -		}
  1.1042 -		else if (current_parse_state == PARSE_STATE_MAIN)
  1.1043 -		{
  1.1044 -			current_parse_state = PARSE_STATE_NONE;
  1.1045 -		}
  1.1046 -	}
  1.1047 -
  1.1048 -	return SMCResult_Continue;
  1.1049 -}
  1.1050 -
  1.1051 -unsigned int TopMenu::FindCategory(const char *name)
  1.1052 -{
  1.1053 -	topmenu_object_t **p_obj = m_ObjLookup.retrieve(name);
  1.1054 -	if (!p_obj)
  1.1055 -	{
  1.1056 -		return 0;
  1.1057 -	}
  1.1058 -
  1.1059 -	topmenu_object_t *obj = *p_obj;
  1.1060 -	if (obj->type != TopMenuObject_Category)
  1.1061 -	{
  1.1062 -		return 0;
  1.1063 -	}
  1.1064 -
  1.1065 -	return obj->object_id;
  1.1066 -}
  1.1067 -
  1.1068 -int _SortObjectNamesDescending(const void *ptr1, const void *ptr2)
  1.1069 -{
  1.1070 -	obj_by_name_t *obj1 = (obj_by_name_t *)ptr1;
  1.1071 -	obj_by_name_t *obj2 = (obj_by_name_t *)ptr2;
  1.1072 -	return strcmp(obj1->name, obj2->name);
  1.1073 -}
  1.1074 -
  1.1075 -unsigned int strncopy(char *dest, const char *src, size_t count)
  1.1076 -{
  1.1077 -	if (!count)
  1.1078 -	{
  1.1079 -		return 0;
  1.1080 -	}
  1.1081 -
  1.1082 -	char *start = dest;
  1.1083 -	while ((*src) && (--count))
  1.1084 -	{
  1.1085 -		*dest++ = *src++;
  1.1086 -	}
  1.1087 -	*dest = '\0';
  1.1088 -
  1.1089 -	return (dest - start);
  1.1090 -}
  1.1091 -
  1.1092 -size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
  1.1093 -{
  1.1094 -	va_list ap;
  1.1095 -	va_start(ap, fmt);
  1.1096 -	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
  1.1097 -	va_end(ap);
  1.1098 -
  1.1099 -	if (len >= maxlength)
  1.1100 -	{
  1.1101 -		buffer[maxlength - 1] = '\0';
  1.1102 -		return (maxlength - 1);
  1.1103 -	}
  1.1104 -	else
  1.1105 -	{
  1.1106 -		return len;
  1.1107 -	}
  1.1108 -}
  1.1109 +/**
  1.1110 + * vim: set ts=4 :
  1.1111 + * =============================================================================
  1.1112 + * SourceMod Sample Extension
  1.1113 + * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
  1.1114 + * =============================================================================
  1.1115 + *
  1.1116 + * This program is free software; you can redistribute it and/or modify it under
  1.1117 + * the terms of the GNU General Public License, version 3.0, as published by the
  1.1118 + * Free Software Foundation.
  1.1119 + * 
  1.1120 + * This program is distributed in the hope that it will be useful, but WITHOUT
  1.1121 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  1.1122 + * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  1.1123 + * details.
  1.1124 + *
  1.1125 + * You should have received a copy of the GNU General Public License along with
  1.1126 + * this program.  If not, see <http://www.gnu.org/licenses/>.
  1.1127 + *
  1.1128 + * As a special exception, AlliedModders LLC gives you permission to link the
  1.1129 + * code of this program (as well as its derivative works) to "Half-Life 2," the
  1.1130 + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
  1.1131 + * by the Valve Corporation.  You must obey the GNU General Public License in
  1.1132 + * all respects for all other code used.  Additionally, AlliedModders LLC grants
  1.1133 + * this exception to all derivative works.  AlliedModders LLC defines further
  1.1134 + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
  1.1135 + * or <http://www.sourcemod.net/license.php>.
  1.1136 + *
  1.1137 + * Version: $Id$
  1.1138 + */
  1.1139 +
  1.1140 +#include <stdlib.h>
  1.1141 +#include <stdarg.h>
  1.1142 +#include "TopMenu.h"
  1.1143 +
  1.1144 +struct obj_by_name_t
  1.1145 +{
  1.1146 +	unsigned int obj_index;
  1.1147 +	char name[64];
  1.1148 +};
  1.1149 +
  1.1150 +int _SortObjectNamesDescending(const void *ptr1, const void *ptr2);
  1.1151 +unsigned int strncopy(char *dest, const char *src, size_t count);
  1.1152 +size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
  1.1153 +
  1.1154 +TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks)
  1.1155 +{
  1.1156 +	m_clients = NULL;
  1.1157 +	m_SerialNo = 1;
  1.1158 +	m_pTitle = callbacks;
  1.1159 +	m_max_clients = 0;
  1.1160 +
  1.1161 +	if (playerhelpers->IsServerActivated())
  1.1162 +	{
  1.1163 +		CreatePlayers(playerhelpers->GetMaxClients());
  1.1164 +	}
  1.1165 +}
  1.1166 +
  1.1167 +TopMenu::~TopMenu()
  1.1168 +{
  1.1169 +	/* Delete all categories */
  1.1170 +	while (m_Categories.size())
  1.1171 +	{
  1.1172 +		RemoveFromMenu(m_Categories[0]->obj->object_id);
  1.1173 +	}
  1.1174 +
  1.1175 +	/* Remove all objects */
  1.1176 +	for (size_t i = 0; i < m_Objects.size(); i++)
  1.1177 +	{
  1.1178 +		assert(m_Objects[i]->is_free == true);
  1.1179 +		delete m_Objects[i];
  1.1180 +	}
  1.1181 +
  1.1182 +	m_pTitle->OnTopMenuObjectRemoved(this, 0);
  1.1183 +
  1.1184 +	/* Delete all cached config entries */
  1.1185 +	for (size_t i = 0; i < m_Config.cats.size(); i++)
  1.1186 +	{
  1.1187 +		delete m_Config.cats[i];
  1.1188 +	}
  1.1189 +
  1.1190 +	/* Sweep players */
  1.1191 +	for (size_t i = 0; i <= (size_t)m_max_clients; i++)
  1.1192 +	{
  1.1193 +		TearDownClient(&m_clients[i]);
  1.1194 +	}
  1.1195 +	delete [] m_clients;
  1.1196 +}
  1.1197 +
  1.1198 +void TopMenu::OnClientConnected(int client)
  1.1199 +{
  1.1200 +	if (m_clients == NULL)
  1.1201 +	{
  1.1202 +		return;
  1.1203 +	}
  1.1204 +
  1.1205 +	topmenu_player_t *player = &m_clients[client];
  1.1206 +	TearDownClient(player);
  1.1207 +}
  1.1208 +
  1.1209 +void TopMenu::OnClientDisconnected(int client)
  1.1210 +{
  1.1211 +	if (m_clients == NULL)
  1.1212 +	{
  1.1213 +		return;
  1.1214 +	}
  1.1215 +
  1.1216 +	topmenu_player_t *player = &m_clients[client];
  1.1217 +	TearDownClient(player);
  1.1218 +}
  1.1219 +
  1.1220 +void TopMenu::OnServerActivated(int max_clients)
  1.1221 +{
  1.1222 +	if (m_clients == NULL)
  1.1223 +	{
  1.1224 +		CreatePlayers(max_clients);
  1.1225 +	}
  1.1226 +}
  1.1227 +
  1.1228 +unsigned int TopMenu::AddToMenu(const char *name,
  1.1229 +								TopMenuObjectType type,
  1.1230 +								ITopMenuObjectCallbacks *callbacks,
  1.1231 +								IdentityToken_t *owner,
  1.1232 +								const char *cmdname,
  1.1233 +								FlagBits flags,
  1.1234 +								unsigned int parent)
  1.1235 +{
  1.1236 +	return AddToMenu2(name, type, callbacks, owner, cmdname, flags, parent, NULL);
  1.1237 +}
  1.1238 +
  1.1239 +unsigned int TopMenu::AddToMenu2(const char *name,
  1.1240 +								 TopMenuObjectType type,
  1.1241 +								 ITopMenuObjectCallbacks *callbacks,
  1.1242 +								 IdentityToken_t *owner,
  1.1243 +								 const char *cmdname,
  1.1244 +								 FlagBits flags,
  1.1245 +								 unsigned int parent,
  1.1246 +								 const char *info_string)
  1.1247 +{
  1.1248 +	/* Sanity checks */
  1.1249 +	if (type == TopMenuObject_Category && parent != 0)
  1.1250 +	{
  1.1251 +		return 0;
  1.1252 +	}
  1.1253 +	else if (type == TopMenuObject_Item && parent == 0)
  1.1254 +	{
  1.1255 +		return 0;
  1.1256 +	}
  1.1257 +	else if (m_ObjLookup.retrieve(name) != NULL)
  1.1258 +	{
  1.1259 +		return 0;
  1.1260 +	}
  1.1261 +	else if (type != TopMenuObject_Item && type != TopMenuObject_Category)
  1.1262 +	{
  1.1263 +		return 0;
  1.1264 +	}
  1.1265 +
  1.1266 +	/* If we're adding an item, make sure the parent is valid, 
  1.1267 +	 * and that the parent is a category.
  1.1268 +	 */
  1.1269 +	topmenu_object_t *parent_obj = NULL;
  1.1270 +	topmenu_category_t *parent_cat = NULL;
  1.1271 +	if (type == TopMenuObject_Item)
  1.1272 +	{
  1.1273 +		/* Check parent index.  Note it will be >= 1 here. */
  1.1274 +		if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free)
  1.1275 +		{
  1.1276 +			return 0;
  1.1277 +		}
  1.1278 +		parent_obj = m_Objects[parent - 1];
  1.1279 +
  1.1280 +		/* Find an equivalent pointer in the category array. */
  1.1281 +		for (size_t i = 0; i < m_Categories.size(); i++)
  1.1282 +		{
  1.1283 +			if (m_Categories[i]->obj == parent_obj)
  1.1284 +			{
  1.1285 +				parent_cat = m_Categories[i];
  1.1286 +				break;
  1.1287 +			}
  1.1288 +		}
  1.1289 +
  1.1290 +		/* If none was found, leave. */
  1.1291 +		if (parent_cat == NULL)
  1.1292 +		{
  1.1293 +			return 0;
  1.1294 +		}
  1.1295 +	}
  1.1296 +
  1.1297 +	/* Re-use an old object pointer if we can. */
  1.1298 +	topmenu_object_t *obj = NULL;
  1.1299 +	for (size_t i = 0; i < m_Objects.size(); i++)
  1.1300 +	{
  1.1301 +		if (m_Objects[i]->is_free == true)
  1.1302 +		{
  1.1303 +			obj = m_Objects[i];
  1.1304 +			break;
  1.1305 +		}
  1.1306 +	}
  1.1307 +
  1.1308 +	/* Otherwise, allocate a new one. */
  1.1309 +	if (obj == NULL)
  1.1310 +	{
  1.1311 +		obj = new topmenu_object_t;
  1.1312 +		obj->object_id = ((unsigned int)m_Objects.size()) + 1;
  1.1313 +		m_Objects.push_back(obj);
  1.1314 +	}
  1.1315 +
  1.1316 +	/* Initialize the object's properties. */
  1.1317 +	obj->callbacks = callbacks;
  1.1318 +	obj->flags = flags;
  1.1319 +	obj->owner = owner;
  1.1320 +	obj->type = type;
  1.1321 +	obj->is_free = false;
  1.1322 +	obj->parent = parent_obj;
  1.1323 +	strncopy(obj->name, name, sizeof(obj->name));
  1.1324 +	strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname));
  1.1325 +	strncopy(obj->info, info_string ? info_string : "", sizeof(obj->info));
  1.1326 +
  1.1327 +	if (obj->type == TopMenuObject_Category)
  1.1328 +	{
  1.1329 +		/* Create a new category entry */
  1.1330 +		topmenu_category_t *cat = new topmenu_category_t;
  1.1331 +		cat->obj = obj;
  1.1332 +		cat->reorder = false;
  1.1333 +		cat->serial = 1;
  1.1334 +
  1.1335 +		/* Add it, then update our serial change number. */
  1.1336 +		m_Categories.push_back(cat);
  1.1337 +		m_SerialNo++;
  1.1338 +
  1.1339 +		/* Updating sorting info */
  1.1340 +		m_bCatsNeedResort = true;
  1.1341 +	}
  1.1342 +	else if (obj->type == TopMenuObject_Item)
  1.1343 +	{
  1.1344 +		/* Update the category, mark it as needing changes */
  1.1345 +		parent_cat->obj_list.push_back(obj);
  1.1346 +		parent_cat->reorder = true;
  1.1347 +		parent_cat->serial++;
  1.1348 +
  1.1349 +		/* If the category just went from 0 to 1 items, mark it as 
  1.1350 +		 * changed, so clients get the category drawn.
  1.1351 +		 */
  1.1352 +		if (parent_cat->obj_list.size() == 1)
  1.1353 +		{
  1.1354 +			m_SerialNo++;
  1.1355 +		}
  1.1356 +	}
  1.1357 +
  1.1358 +	m_ObjLookup.insert(name, obj);
  1.1359 +
  1.1360 +	return obj->object_id;
  1.1361 +}
  1.1362 +
  1.1363 +const char *TopMenu::GetObjectInfoString(unsigned int object_id)
  1.1364 +{
  1.1365 +	if (object_id == 0 
  1.1366 +		|| object_id > m_Objects.size() 
  1.1367 +		|| m_Objects[object_id - 1]->is_free)
  1.1368 +	{
  1.1369 +		return NULL;
  1.1370 +	}
  1.1371 +
  1.1372 +	topmenu_object_t *obj = m_Objects[object_id - 1];
  1.1373 +
  1.1374 +	return obj->info;
  1.1375 +}
  1.1376 +
  1.1377 +const char *TopMenu::GetObjectName(unsigned int object_id)
  1.1378 +{
  1.1379 +	if (object_id == 0 
  1.1380 +		|| object_id > m_Objects.size() 
  1.1381 +		|| m_Objects[object_id - 1]->is_free)
  1.1382 +	{
  1.1383 +		return NULL;
  1.1384 +	}
  1.1385 +
  1.1386 +	topmenu_object_t *obj = m_Objects[object_id - 1];
  1.1387 +
  1.1388 +	return obj->name;
  1.1389 +}
  1.1390 +
  1.1391 +void TopMenu::RemoveFromMenu(unsigned int object_id)
  1.1392 +{
  1.1393 +	if (object_id == 0 
  1.1394 +		|| object_id > m_Objects.size() 
  1.1395 +		|| m_Objects[object_id - 1]->is_free)
  1.1396 +	{
  1.1397 +		return;
  1.1398 +	}
  1.1399 +
  1.1400 +	topmenu_object_t *obj = m_Objects[object_id - 1];
  1.1401 +
  1.1402 +	m_ObjLookup.remove(obj->name);
  1.1403 +
  1.1404 +	if (obj->type == TopMenuObject_Category)
  1.1405 +	{
  1.1406 +		/* Find it in the category list. */
  1.1407 +		for (size_t i = 0; i < m_Categories.size(); i++)
  1.1408 +		{
  1.1409 +			if (m_Categories[i]->obj == obj)
  1.1410 +			{
  1.1411 +				/* Mark all children as removed + free.  Note we could
  1.1412 +				 * call into RemoveMenuItem() for this, but it'd be very 
  1.1413 +				 * inefficient!
  1.1414 +				 */
  1.1415 +				topmenu_category_t *cat = m_Categories[i];
  1.1416 +				for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++)
  1.1417 +				{
  1.1418 +					cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id);
  1.1419 +					cat->obj_list[j]->is_free = true;
  1.1420 +				}
  1.1421 +				
  1.1422 +				/* Remove the category from the list, then delete it. */
  1.1423 +				m_Categories.erase(m_Categories.iterAt(i));
  1.1424 +				delete cat;
  1.1425 +				break;
  1.1426 +			}
  1.1427 +		}
  1.1428 +
  1.1429 +		/* Update the root as changed. */
  1.1430 +		m_SerialNo++;
  1.1431 +		m_bCatsNeedResort = true;
  1.1432 +	}
  1.1433 +	else if (obj->type == TopMenuObject_Item)
  1.1434 +	{
  1.1435 +		/* Find the category this item is in. */
  1.1436 +		topmenu_category_t *parent_cat = NULL;
  1.1437 +		for (size_t i = 0; i < m_Categories.size(); i++)
  1.1438 +		{
  1.1439 +			if (m_Categories[i]->obj == obj->parent)
  1.1440 +			{
  1.1441 +				parent_cat = m_Categories[i];
  1.1442 +				break;
  1.1443 +			}
  1.1444 +		}
  1.1445 +
  1.1446 +		/* Erase it from the category's lists. */
  1.1447 +		if (parent_cat)
  1.1448 +		{
  1.1449 +			for (size_t i = 0; i < parent_cat->obj_list.size(); i++)
  1.1450 +			{
  1.1451 +				if (parent_cat->obj_list[i] == obj)
  1.1452 +				{
  1.1453 +					parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i));
  1.1454 +
  1.1455 +					/* If this category now has no items, mark root as changed 
  1.1456 +					 * so clients won't get the category drawn anymore.
  1.1457 +					 */
  1.1458 +					if (parent_cat->obj_list.size() == 0)
  1.1459 +					{
  1.1460 +						m_SerialNo++;
  1.1461 +					}
  1.1462 +					break;
  1.1463 +				}
  1.1464 +			}
  1.1465 +
  1.1466 +			/* Update the category as changed. */
  1.1467 +			parent_cat->reorder = true;
  1.1468 +			parent_cat->serial++;
  1.1469 +		}
  1.1470 +	}
  1.1471 +
  1.1472 +	/* The callbacks pointer is still valid, so fire away! */
  1.1473 +	obj->callbacks->OnTopMenuObjectRemoved(this, object_id);
  1.1474 +
  1.1475 +	/* Finally, mark the object as free. */
  1.1476 +	obj->is_free = true;
  1.1477 +}
  1.1478 +
  1.1479 +bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position)
  1.1480 +{
  1.1481 +	if (m_clients == NULL)
  1.1482 +	{
  1.1483 +		return false;
  1.1484 +	}
  1.1485 +
  1.1486 +	IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
  1.1487 +	if (!pPlayer->IsInGame())
  1.1488 +	{
  1.1489 +		return false;
  1.1490 +	}
  1.1491 +
  1.1492 +	UpdateClientRoot(client, pPlayer);
  1.1493 +
  1.1494 +	topmenu_player_t *pClient = &m_clients[client];
  1.1495 +	if (pClient->root == NULL)
  1.1496 +	{
  1.1497 +		return false;
  1.1498 +	}
  1.1499 +
  1.1500 +	bool return_value = false;
  1.1501 +
  1.1502 +	if (position == TopMenuPosition_LastCategory && 
  1.1503 +		pClient->last_category < m_Categories.size())
  1.1504 +	{
  1.1505 +		return_value = DisplayCategory(client, pClient->last_category, hold_time, true);
  1.1506 +		if (!return_value)
  1.1507 +		{
  1.1508 +			return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
  1.1509 +		}
  1.1510 +	}
  1.1511 +	else if (position == TopMenuPosition_LastRoot)
  1.1512 +	{
  1.1513 +		pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
  1.1514 +	}
  1.1515 +	else if (position == TopMenuPosition_Start)
  1.1516 +	{
  1.1517 +		pClient->last_position = 0;
  1.1518 +		pClient->last_category = 0;
  1.1519 +		return_value = pClient->root->Display(client, hold_time);
  1.1520 +	}
  1.1521 +
  1.1522 +	return return_value;
  1.1523 +}
  1.1524 +
  1.1525 +bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position)
  1.1526 +{
  1.1527 +	UpdateClientCategory(client, category);
  1.1528 +
  1.1529 +	topmenu_player_t *pClient = &m_clients[client];
  1.1530 +	if (category >= pClient->cat_count
  1.1531 +		|| pClient->cats[category].menu == NULL)
  1.1532 +	{
  1.1533 +		return false;
  1.1534 +	}
  1.1535 +
  1.1536 +	bool return_value = false;
  1.1537 +
  1.1538 +	topmenu_player_category_t *player_cat = &(pClient->cats[category]);
  1.1539 +
  1.1540 +	pClient->last_category = category;
  1.1541 +	if (last_position)
  1.1542 +	{
  1.1543 +		return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position);
  1.1544 +	}
  1.1545 +	else
  1.1546 +	{
  1.1547 +		return_value = player_cat->menu->Display(client, hold_time);
  1.1548 +	}
  1.1549 +
  1.1550 +	return return_value;
  1.1551 +}
  1.1552 +
  1.1553 +void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
  1.1554 +{
  1.1555 +	const char *item_name = menu->GetItemInfo(item, NULL);
  1.1556 +	if (!item_name)
  1.1557 +	{
  1.1558 +		return;
  1.1559 +	}
  1.1560 +
  1.1561 +	topmenu_object_t *obj;
  1.1562 +	topmenu_player_t *pClient = &m_clients[client];
  1.1563 +	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
  1.1564 +	if (pObject == NULL)
  1.1565 +	{
  1.1566 +		return;
  1.1567 +	}
  1.1568 +
  1.1569 +	obj = *pObject;
  1.1570 +
  1.1571 +	/* We now have the object... what do we do with it? */
  1.1572 +	if (obj->type == TopMenuObject_Category)
  1.1573 +	{
  1.1574 +		/* If it's a category, the user wants to view it.. */
  1.1575 +		for (size_t i = 0; i < m_Categories.size(); i++)
  1.1576 +		{
  1.1577 +			if (m_Categories[i]->obj == obj)
  1.1578 +			{
  1.1579 +				pClient->last_root_pos = item_on_page;
  1.1580 +				if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false))
  1.1581 +				{
  1.1582 +					/* If we can't display the category, re-display the root menu */
  1.1583 +					DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot);
  1.1584 +				}
  1.1585 +				break;
  1.1586 +			}
  1.1587 +		}
  1.1588 +	}
  1.1589 +	else
  1.1590 +	{
  1.1591 +		pClient->last_position = item_on_page;
  1.1592 +		
  1.1593 +		/* Re-check access in case this user had their credentials revoked */
  1.1594 +		if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
  1.1595 +		{
  1.1596 +			DisplayMenu(client, 0, TopMenuPosition_LastCategory);
  1.1597 +			return;
  1.1598 +		}
  1.1599 +		
  1.1600 +		/* Pass the information on to the callback */
  1.1601 +		obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id);
  1.1602 +	}
  1.1603 +}
  1.1604 +
  1.1605 +void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
  1.1606 +{
  1.1607 +	const char *item_name = menu->GetItemInfo(item, NULL);
  1.1608 +	if (!item_name)
  1.1609 +	{
  1.1610 +		return;
  1.1611 +	}
  1.1612 +
  1.1613 +	topmenu_object_t *obj;
  1.1614 +	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
  1.1615 +	if (pObject == NULL)
  1.1616 +	{
  1.1617 +		return;
  1.1618 +	}
  1.1619 +
  1.1620 +	obj = *pObject;
  1.1621 +
  1.1622 +	style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id);
  1.1623 +	if (style != ITEMDRAW_DEFAULT)
  1.1624 +	{
  1.1625 +		return;
  1.1626 +	}
  1.1627 +
  1.1628 +	if (obj->cmdname[0] == '\0')
  1.1629 +	{
  1.1630 +		return;
  1.1631 +	}
  1.1632 +
  1.1633 +	if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
  1.1634 +	{
  1.1635 +		style = ITEMDRAW_IGNORE;
  1.1636 +	}
  1.1637 +}
  1.1638 +
  1.1639 +unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu,
  1.1640 +								int client,
  1.1641 +								IMenuPanel *panel,
  1.1642 +								unsigned int item,
  1.1643 +								const ItemDrawInfo &dr)
  1.1644 +{
  1.1645 +	const char *item_name = menu->GetItemInfo(item, NULL);
  1.1646 +	if (!item_name)
  1.1647 +	{
  1.1648 +		return 0;
  1.1649 +	}
  1.1650 +
  1.1651 +	topmenu_object_t *obj;
  1.1652 +	topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
  1.1653 +	if (pObject == NULL)
  1.1654 +	{
  1.1655 +		return 0;
  1.1656 +	}
  1.1657 +
  1.1658 +	obj = *pObject;
  1.1659 +
  1.1660 +	/* Ask the object to render the text for this client */
  1.1661 +	char renderbuf[64];
  1.1662 +	obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf));
  1.1663 +
  1.1664 +	/* Build the new draw info */
  1.1665 +	ItemDrawInfo new_dr = dr;
  1.1666 +	new_dr.display = renderbuf;
  1.1667 +
  1.1668 +	/* Man I love the menu API.  Ask the panel to draw the item and give the position 
  1.1669 +	 * back to Core's renderer.  This way we don't have to worry about keeping the 
  1.1670 +	 * render buffer static!
  1.1671 +	 */
  1.1672 +	return panel->DrawItem(new_dr);
  1.1673 +}
  1.1674 +
  1.1675 +void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
  1.1676 +{
  1.1677 +	if (reason == MenuCancel_ExitBack)
  1.1678 +	{
  1.1679 +		/* If the client chose exit back, they were on a category menu, and we can
  1.1680 +		 * now display the root menu from the last known position.
  1.1681 +		 */
  1.1682 +		DisplayMenu(client, 0, TopMenuPosition_LastRoot);
  1.1683 +	}
  1.1684 +}
  1.1685 +
  1.1686 +void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer)
  1.1687 +{
  1.1688 +	topmenu_player_t *pClient = &m_clients[client];
  1.1689 +	IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client);
  1.1690 +
  1.1691 +	/* Determine if an update is necessary */
  1.1692 +	bool is_update_needed = false;
  1.1693 +	if (pClient->menu_serial != m_SerialNo)
  1.1694 +	{
  1.1695 +		is_update_needed = true;
  1.1696 +	}
  1.1697 +	else if (pPlayer->GetUserId() != pClient->user_id)
  1.1698 +	{
  1.1699 +		is_update_needed = true;
  1.1700 +	}
  1.1701 +
  1.1702 +	/* If no update is needed at the root level, just leave now */
  1.1703 +	if (!is_update_needed)
  1.1704 +	{
  1.1705 +		return;
  1.1706 +	}
  1.1707 +
  1.1708 +	/* First we need to flush the cache... */
  1.1709 +	TearDownClient(pClient);
  1.1710 +
  1.1711 +	/* Now, rebuild the category list, but don't create menus */
  1.1712 +	if (m_Categories.size() == 0)
  1.1713 +	{
  1.1714 +		pClient->cat_count = 0;
  1.1715 +		pClient->cats = NULL;
  1.1716 +	}
  1.1717 +	else
  1.1718 +	{
  1.1719 +		pClient->cat_count = (unsigned int)m_Categories.size();
  1.1720 +		pClient->cats = new topmenu_player_category_t[pClient->cat_count];
  1.1721 +		memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count);
  1.1722 +	}
  1.1723 +
  1.1724 +	/* Re-sort the root categories if needed */
  1.1725 +	SortCategoriesIfNeeded();
  1.1726 +
  1.1727 +	/* Build the root menu */
  1.1728 +	IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
  1.1729 +
  1.1730 +	/* Add the sorted items */
  1.1731 +	for (size_t i = 0; i < m_SortedCats.size(); i++)
  1.1732 +	{
  1.1733 +		if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0)
  1.1734 +		{
  1.1735 +			continue;
  1.1736 +		}
  1.1737 +		root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo(""));
  1.1738 +	}
  1.1739 +
  1.1740 +	/* Now we need to handle un-sorted items.  This is slightly trickier, as we need to 
  1.1741 +	 * pre-render each category name, and cache those names.  Phew!
  1.1742 +	 */
  1.1743 +	if (m_UnsortedCats.size())
  1.1744 +	{
  1.1745 +		obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()];
  1.1746 +		for (size_t i = 0; i < m_UnsortedCats.size(); i++)
  1.1747 +		{
  1.1748 +			obj_by_name_t *temp_obj = &item_list[i];
  1.1749 +			topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj;
  1.1750 +			obj->callbacks->OnTopMenuDisplayOption(this,
  1.1751 +				client, 
  1.1752 +				obj->object_id,
  1.1753 +				temp_obj->name,
  1.1754 +				sizeof(temp_obj->name));
  1.1755 +			temp_obj->obj_index = m_UnsortedCats[i];
  1.1756 +		}
  1.1757 +
  1.1758 +		/* Sort our temp list */
  1.1759 +		qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
  1.1760 +
  1.1761 +		/* Add the new sorted categories */
  1.1762 +		for (size_t i = 0; i < m_UnsortedCats.size(); i++)
  1.1763 +		{
  1.1764 +			if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0)
  1.1765 +			{
  1.1766 +				continue;
  1.1767 +			}
  1.1768 +			root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo(""));
  1.1769 +		}
  1.1770 +
  1.1771 +		delete [] item_list;
  1.1772 +	}
  1.1773 +
  1.1774 +	/* Set the menu's title */
  1.1775 +	char renderbuf[128];
  1.1776 +	m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf));
  1.1777 +	root_menu->SetDefaultTitle(renderbuf);
  1.1778 +
  1.1779 +	/* The client is now fully updated */
  1.1780 +	pClient->root = root_menu;
  1.1781 +	pClient->user_id = pPlayer->GetUserId();
  1.1782 +	pClient->menu_serial = m_SerialNo;
  1.1783 +	pClient->last_position = 0;
  1.1784 +	pClient->last_category = 0;
  1.1785 +	pClient->last_root_pos = 0;
  1.1786 +}
  1.1787 +
  1.1788 +void TopMenu::UpdateClientCategory(int client, unsigned int category)
  1.1789 +{
  1.1790 +	/* Update the client's root menu just in case it needs it.  This 
  1.1791 +	 * will validate that we have both a valid client and a valid
  1.1792 +	 * category structure for that client.
  1.1793 +	 */
  1.1794 +	UpdateClientRoot(client);
  1.1795 +
  1.1796 +	/* Now it's guaranteed that our category tables will be usable */
  1.1797 +	topmenu_player_t *pClient = &m_clients[client];
  1.1798 +	topmenu_category_t *cat = m_Categories[category];
  1.1799 +	topmenu_player_category_t *player_cat = &(pClient->cats[category]);
  1.1800 +
  1.1801 +	/* Does the category actually need updating? */
  1.1802 +	if (player_cat->serial == cat->serial)
  1.1803 +	{
  1.1804 +		return;
  1.1805 +	}
  1.1806 +
  1.1807 +	/* Destroy any existing menu */
  1.1808 +	if (player_cat->menu)
  1.1809 +	{
  1.1810 +		player_cat->menu->Destroy();
  1.1811 +		player_cat->menu = NULL;
  1.1812 +	}
  1.1813 +
  1.1814 +	if (pClient->last_category == category)
  1.1815 +	{
  1.1816 +		pClient->last_position = 0;
  1.1817 +	}
  1.1818 +
  1.1819 +	IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
  1.1820 +	
  1.1821 +	/* Categories get an "exit back" button */
  1.1822 +	cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK);
  1.1823 +
  1.1824 +	/* Re-sort the category if needed */
  1.1825 +	SortCategoryIfNeeded(category);
  1.1826 +
  1.1827 +	/* Build the menu with the sorted items first */
  1.1828 +	for (size_t i = 0; i < cat->sorted.size(); i++)
  1.1829 +	{
  1.1830 +		cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo(""));
  1.1831 +	}
  1.1832 +
  1.1833 +	/* Now handle unsorted items */
  1.1834 +	if (cat->unsorted.size())
  1.1835 +	{
  1.1836 +		/* Build a list of the item names */
  1.1837 +		obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()];
  1.1838 +		for (size_t i = 0; i < cat->unsorted.size(); i++)
  1.1839 +		{
  1.1840 +			obj_by_name_t *item = &item_list[i];
  1.1841 +			topmenu_object_t *obj = cat->unsorted[i];
  1.1842 +			obj->callbacks->OnTopMenuDisplayOption(this,
  1.1843 +				client,
  1.1844 +				obj->object_id,
  1.1845 +				item->name,
  1.1846 +				sizeof(item->name));
  1.1847 +			item->obj_index = (unsigned int)i;
  1.1848 +		}
  1.1849 +
  1.1850 +		/* Sort the names */
  1.1851 +		qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
  1.1852 +
  1.1853 +		/* Add to the menu */
  1.1854 +		for (size_t i = 0; i < cat->unsorted.size(); i++)
  1.1855 +		{
  1.1856 +			cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo(""));
  1.1857 +		}
  1.1858 +
  1.1859 +		delete [] item_list;
  1.1860 +	}
  1.1861 +
  1.1862 +	/* Set the menu's title */
  1.1863 +	char renderbuf[128];
  1.1864 +	cat->obj->callbacks->OnTopMenuDisplayTitle(this, 
  1.1865 +		client, 
  1.1866 +		cat->obj->object_id, 
  1.1867 +		renderbuf, 
  1.1868 +		sizeof(renderbuf));
  1.1869 +	cat_menu->SetDefaultTitle(renderbuf);
  1.1870 +
  1.1871 +	/* We're done! */
  1.1872 +	player_cat->menu = cat_menu;
  1.1873 +	player_cat->serial = cat->serial;
  1.1874 +}
  1.1875 +
  1.1876 +void TopMenu::SortCategoryIfNeeded(unsigned int category)
  1.1877 +{
  1.1878 +	topmenu_category_t *cat = m_Categories[category];
  1.1879 +	if (!cat->reorder)
  1.1880 +	{
  1.1881 +		return;
  1.1882 +	}
  1.1883 +
  1.1884 +	cat->sorted.clear();
  1.1885 +	cat->unsorted.clear();
  1.1886 +
  1.1887 +	if (cat->obj_list.size() == 0)
  1.1888 +	{
  1.1889 +		cat->reorder = false;
  1.1890 +		return;
  1.1891 +	}
  1.1892 +
  1.1893 +	CVector<unsigned int> to_sort;
  1.1894 +	for (size_t i = 0; i < cat->obj_list.size(); i++)
  1.1895 +	{
  1.1896 +		to_sort.push_back(i);
  1.1897 +	}
  1.1898 +
  1.1899 +	/* Find a matching category in the configs */
  1.1900 +	config_category_t *config_cat = NULL; 
  1.1901 +	for (size_t i = 0; i < m_Config.cats.size(); i++)
  1.1902 +	{
  1.1903 +		if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0)
  1.1904 +		{
  1.1905 +			config_cat = m_Config.cats[i];
  1.1906 +			break;
  1.1907 +		}
  1.1908 +	}
  1.1909 +
  1.1910 +	/* If there is a matching category, build a pre-sorted item list */
  1.1911 +	if (config_cat != NULL)
  1.1912 +	{
  1.1913 +		/* Find matching objects in this category */
  1.1914 +		for (size_t i = 0; i < config_cat->commands.size(); i++)
  1.1915 +		{
  1.1916 +			const char *config_name = m_Config.strings.GetString(config_cat->commands[i]);
  1.1917 +			for (size_t j = 0; j < to_sort.size(); j++)
  1.1918 +			{
  1.1919 +				if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0)
  1.1920 +				{
  1.1921 +					/* Place in the final list, then remove from the temporary list */
  1.1922 +					cat->sorted.push_back(cat->obj_list[to_sort[j]]);
  1.1923 +					to_sort.erase(to_sort.iterAt(j));
  1.1924 +					break;
  1.1925 +				}
  1.1926 +			}
  1.1927 +		}
  1.1928 +	}
  1.1929 +
  1.1930 +	/* Push any remaining items onto the unsorted list */
  1.1931 +	for (size_t i = 0; i < to_sort.size(); i++)
  1.1932 +	{
  1.1933 +		cat->unsorted.push_back(cat->obj_list[to_sort[i]]);
  1.1934 +	}
  1.1935 +
  1.1936 +	cat->reorder = false;
  1.1937 +}
  1.1938 +
  1.1939 +void TopMenu::SortCategoriesIfNeeded()
  1.1940 +{
  1.1941 +	if (!m_bCatsNeedResort)
  1.1942 +	{
  1.1943 +		return;
  1.1944 +	}
  1.1945 +
  1.1946 +	/* Clear sort results */
  1.1947 +	m_SortedCats.clear();
  1.1948 +	m_UnsortedCats.clear();
  1.1949 +
  1.1950 +	if (m_Categories.size() == 0)
  1.1951 +	{
  1.1952 +		m_bCatsNeedResort = false;
  1.1953 +		return;
  1.1954 +	}
  1.1955 +
  1.1956 +	CVector<unsigned int> to_sort;
  1.1957 +	for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++)
  1.1958 +	{
  1.1959 +		to_sort.push_back(i);
  1.1960 +	}
  1.1961 +
  1.1962 +	/* If we have any predefined categories, add them in as they appear. */
  1.1963 +	for (size_t i= 0; i < m_Config.cats.size(); i++)
  1.1964 +	{
  1.1965 +		/* Find this category and map it in if we can */
  1.1966 +		for (size_t j = 0; j < to_sort.size(); j++)
  1.1967 +		{
  1.1968 +			if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), 
  1.1969 +					   m_Categories[to_sort[j]]->obj->name) == 0)
  1.1970 +			{
  1.1971 +				/* Add to the real list and remove from the temporary */
  1.1972 +				m_SortedCats.push_back(to_sort[j]);
  1.1973 +				to_sort.erase(to_sort.iterAt(j));
  1.1974 +				break;
  1.1975 +			}
  1.1976 +		}
  1.1977 +	}
  1.1978 +
  1.1979 +	/* Push any remaining items onto the unsorted list */
  1.1980 +	for (size_t i = 0; i < to_sort.size(); i++)
  1.1981 +	{
  1.1982 +		m_UnsortedCats.push_back(to_sort[i]);
  1.1983 +	}
  1.1984 +
  1.1985 +	m_bCatsNeedResort = false;
  1.1986 +}
  1.1987 +
  1.1988 +void TopMenu::CreatePlayers(int max_clients)
  1.1989 +{
  1.1990 +	m_max_clients = max_clients;
  1.1991 +	m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1));
  1.1992 +	memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1));
  1.1993 +}
  1.1994 +
  1.1995 +void TopMenu::TearDownClient(topmenu_player_t *player)
  1.1996 +{
  1.1997 +	if (player->cats != NULL)
  1.1998 +	{
  1.1999 +		for (unsigned int i = 0; i < player->cat_count; i++)
  1.2000 +		{
  1.2001 +			topmenu_player_category_t *player_cat = &(player->cats[i]);
  1.2002 +			if (player_cat->menu != NULL)
  1.2003 +			{
  1.2004 +				player_cat->menu->Destroy();
  1.2005 +			}
  1.2006 +		}
  1.2007 +		delete [] player->cats;
  1.2008 +	}
  1.2009 +
  1.2010 +	if (player->root != NULL)
  1.2011 +	{
  1.2012 +		player->root->Destroy();
  1.2013 +	}
  1.2014 +
  1.2015 +	memset(player, 0, sizeof(topmenu_player_t));
  1.2016 +}
  1.2017 +
  1.2018 +bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength)
  1.2019 +{
  1.2020 +	SMCError err;
  1.2021 +	SMCStates states;
  1.2022 +
  1.2023 +	if ((err = textparsers->ParseFile_SMC(file, this, &states))
  1.2024 +		!= SMCError_Okay)
  1.2025 +	{
  1.2026 +		const char *err_string = textparsers->GetSMCErrorString(err);
  1.2027 +		if (!err_string)
  1.2028 +		{
  1.2029 +			err_string = "Unknown";
  1.2030 +		}
  1.2031 +
  1.2032 +		UTIL_Format(error, maxlength, "%s", err_string);
  1.2033 +
  1.2034 +		return false;
  1.2035 +	}
  1.2036 +
  1.2037 +	return true;
  1.2038 +}
  1.2039 +
  1.2040 +bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner)
  1.2041 +{
  1.2042 +	/* First sweep the categories owned by us */
  1.2043 +	CVector<unsigned int> obj_list;
  1.2044 +	for (size_t i = 0; i < m_Categories.size(); i++)
  1.2045 +	{
  1.2046 +		if (m_Categories[i]->obj->owner == owner)
  1.2047 +		{
  1.2048 +			obj_list.push_back(m_Categories[i]->obj->object_id);
  1.2049 +		}
  1.2050 +	}
  1.2051 +
  1.2052 +	for (size_t i = 0; i < obj_list.size(); i++)
  1.2053 +	{
  1.2054 +		RemoveFromMenu(obj_list[i]);
  1.2055 +	}
  1.2056 +
  1.2057 +	/* Now we can look for actual items */
  1.2058 +	for (size_t i = 0; i < m_Objects.size(); i++)
  1.2059 +	{
  1.2060 +		if (m_Objects[i]->is_free)
  1.2061 +		{
  1.2062 +			continue;
  1.2063 +		}
  1.2064 +		if (m_Objects[i]->owner == owner)
  1.2065 +		{
  1.2066 +			assert(m_Objects[i]->type != TopMenuObject_Category);
  1.2067 +			RemoveFromMenu(m_Objects[i]->object_id);
  1.2068 +		}
  1.2069 +	}
  1.2070 +
  1.2071 +	return true;
  1.2072 +}
  1.2073 +
  1.2074 +#define PARSE_STATE_NONE		0
  1.2075 +#define PARSE_STATE_MAIN		1
  1.2076 +#define PARSE_STATE_CATEGORY	2
  1.2077 +unsigned int ignore_parse_level = 0;
  1.2078 +unsigned int current_parse_state = 0;
  1.2079 +config_category_t *cur_cat = NULL;
  1.2080 +
  1.2081 +void TopMenu::ReadSMC_ParseStart()
  1.2082 +{
  1.2083 +	current_parse_state = PARSE_STATE_NONE;
  1.2084 +	ignore_parse_level = 0;
  1.2085 +	cur_cat = NULL;
  1.2086 +
  1.2087 +	/* Reset the old config */
  1.2088 +	m_Config.strings.Reset();
  1.2089 +	for (size_t i = 0; i < m_Config.cats.size(); i++)
  1.2090 +	{
  1.2091 +		delete m_Config.cats[i];
  1.2092 +	}
  1.2093 +	m_Config.cats.clear();
  1.2094 +}
  1.2095 +
  1.2096 +SMCResult TopMenu::ReadSMC_NewSection(const SMCStates *states, const char *name)
  1.2097 +{
  1.2098 +	if (ignore_parse_level)
  1.2099 +	{
  1.2100 +		ignore_parse_level++;
  1.2101 +	}
  1.2102 +	else
  1.2103 +	{
  1.2104 +		if (current_parse_state == PARSE_STATE_NONE)
  1.2105 +		{
  1.2106 +			if (strcmp(name, "Menu") == 0)
  1.2107 +			{
  1.2108 +				current_parse_state = PARSE_STATE_MAIN;
  1.2109 +			}
  1.2110 +			else
  1.2111 +			{
  1.2112 +				ignore_parse_level = 1;
  1.2113 +			}
  1.2114 +		}
  1.2115 +		else if (current_parse_state == PARSE_STATE_MAIN)
  1.2116 +		{
  1.2117 +			cur_cat = new config_category_t;
  1.2118 +			cur_cat->name = m_Config.strings.AddString(name);
  1.2119 +			m_Config.cats.push_back(cur_cat);
  1.2120 +			current_parse_state = PARSE_STATE_CATEGORY;
  1.2121 +		}
  1.2122 +		else
  1.2123 +		{
  1.2124 +			ignore_parse_level = 1;
  1.2125 +		}
  1.2126 +	}
  1.2127 +
  1.2128 +	return SMCResult_Continue;
  1.2129 +}
  1.2130 +
  1.2131 +SMCResult TopMenu::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
  1.2132 +{
  1.2133 +	if (ignore_parse_level > 0
  1.2134 +		|| current_parse_state != PARSE_STATE_CATEGORY
  1.2135 +		|| cur_cat == NULL)
  1.2136 +	{
  1.2137 +		return SMCResult_Continue;
  1.2138 +	}
  1.2139 +
  1.2140 +	if (strcmp(key, "item") == 0)
  1.2141 +	{
  1.2142 +		cur_cat->commands.push_back(m_Config.strings.AddString(value));
  1.2143 +	}
  1.2144 +
  1.2145 +	return SMCResult_Continue;
  1.2146 +}
  1.2147 +
  1.2148 +SMCResult TopMenu::ReadSMC_LeavingSection(const SMCStates *states)
  1.2149 +{
  1.2150 +	if (ignore_parse_level)
  1.2151 +	{
  1.2152 +		ignore_parse_level--;
  1.2153 +	}
  1.2154 +	else
  1.2155 +	{
  1.2156 +		if (current_parse_state == PARSE_STATE_CATEGORY)
  1.2157 +		{
  1.2158 +			cur_cat = NULL;
  1.2159 +			current_parse_state = PARSE_STATE_MAIN;
  1.2160 +		}
  1.2161 +		else if (current_parse_state == PARSE_STATE_MAIN)
  1.2162 +		{
  1.2163 +			current_parse_state = PARSE_STATE_NONE;
  1.2164 +		}
  1.2165 +	}
  1.2166 +
  1.2167 +	return SMCResult_Continue;
  1.2168 +}
  1.2169 +
  1.2170 +unsigned int TopMenu::FindCategory(const char *name)
  1.2171 +{
  1.2172 +	topmenu_object_t **p_obj = m_ObjLookup.retrieve(name);
  1.2173 +	if (!p_obj)
  1.2174 +	{
  1.2175 +		return 0;
  1.2176 +	}
  1.2177 +
  1.2178 +	topmenu_object_t *obj = *p_obj;
  1.2179 +	if (obj->type != TopMenuObject_Category)
  1.2180 +	{
  1.2181 +		return 0;
  1.2182 +	}
  1.2183 +
  1.2184 +	return obj->object_id;
  1.2185 +}
  1.2186 +
  1.2187 +int _SortObjectNamesDescending(const void *ptr1, const void *ptr2)
  1.2188 +{
  1.2189 +	obj_by_name_t *obj1 = (obj_by_name_t *)ptr1;
  1.2190 +	obj_by_name_t *obj2 = (obj_by_name_t *)ptr2;
  1.2191 +	return strcmp(obj1->name, obj2->name);
  1.2192 +}
  1.2193 +
  1.2194 +unsigned int strncopy(char *dest, const char *src, size_t count)
  1.2195 +{
  1.2196 +	if (!count)
  1.2197 +	{
  1.2198 +		return 0;
  1.2199 +	}
  1.2200 +
  1.2201 +	char *start = dest;
  1.2202 +	while ((*src) && (--count))
  1.2203 +	{
  1.2204 +		*dest++ = *src++;
  1.2205 +	}
  1.2206 +	*dest = '\0';
  1.2207 +
  1.2208 +	return (dest - start);
  1.2209 +}
  1.2210 +
  1.2211 +size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
  1.2212 +{
  1.2213 +	va_list ap;
  1.2214 +	va_start(ap, fmt);
  1.2215 +	size_t len = vsnprintf(buffer, maxlength, fmt, ap);
  1.2216 +	va_end(ap);
  1.2217 +
  1.2218 +	if (len >= maxlength)
  1.2219 +	{
  1.2220 +		buffer[maxlength - 1] = '\0';
  1.2221 +		return (maxlength - 1);
  1.2222 +	}
  1.2223 +	else
  1.2224 +	{
  1.2225 +		return len;
  1.2226 +	}
  1.2227 +}