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 +}