1.1 --- a/extensions/topmenus/TopMenu.cpp Fri Mar 28 06:21:34 2008 +0000
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,1119 +0,0 @@
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 -const char *TopMenu::GetObjectName(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 NULL;
1.279 - }
1.280 -
1.281 - topmenu_object_t *obj = m_Objects[object_id - 1];
1.282 -
1.283 - return obj->name;
1.284 -}
1.285 -
1.286 -void TopMenu::RemoveFromMenu(unsigned int object_id)
1.287 -{
1.288 - if (object_id == 0
1.289 - || object_id > m_Objects.size()
1.290 - || m_Objects[object_id - 1]->is_free)
1.291 - {
1.292 - return;
1.293 - }
1.294 -
1.295 - topmenu_object_t *obj = m_Objects[object_id - 1];
1.296 -
1.297 - m_ObjLookup.remove(obj->name);
1.298 -
1.299 - if (obj->type == TopMenuObject_Category)
1.300 - {
1.301 - /* Find it in the category list. */
1.302 - for (size_t i = 0; i < m_Categories.size(); i++)
1.303 - {
1.304 - if (m_Categories[i]->obj == obj)
1.305 - {
1.306 - /* Mark all children as removed + free. Note we could
1.307 - * call into RemoveMenuItem() for this, but it'd be very
1.308 - * inefficient!
1.309 - */
1.310 - topmenu_category_t *cat = m_Categories[i];
1.311 - for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++)
1.312 - {
1.313 - cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id);
1.314 - cat->obj_list[j]->is_free = true;
1.315 - }
1.316 -
1.317 - /* Remove the category from the list, then delete it. */
1.318 - m_Categories.erase(m_Categories.iterAt(i));
1.319 - delete cat;
1.320 - break;
1.321 - }
1.322 - }
1.323 -
1.324 - /* Update the root as changed. */
1.325 - m_SerialNo++;
1.326 - m_bCatsNeedResort = true;
1.327 - }
1.328 - else if (obj->type == TopMenuObject_Item)
1.329 - {
1.330 - /* Find the category this item is in. */
1.331 - topmenu_category_t *parent_cat = NULL;
1.332 - for (size_t i = 0; i < m_Categories.size(); i++)
1.333 - {
1.334 - if (m_Categories[i]->obj == obj->parent)
1.335 - {
1.336 - parent_cat = m_Categories[i];
1.337 - break;
1.338 - }
1.339 - }
1.340 -
1.341 - /* Erase it from the category's lists. */
1.342 - if (parent_cat)
1.343 - {
1.344 - for (size_t i = 0; i < parent_cat->obj_list.size(); i++)
1.345 - {
1.346 - if (parent_cat->obj_list[i] == obj)
1.347 - {
1.348 - parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i));
1.349 -
1.350 - /* If this category now has no items, mark root as changed
1.351 - * so clients won't get the category drawn anymore.
1.352 - */
1.353 - if (parent_cat->obj_list.size() == 0)
1.354 - {
1.355 - m_SerialNo++;
1.356 - }
1.357 - break;
1.358 - }
1.359 - }
1.360 -
1.361 - /* Update the category as changed. */
1.362 - parent_cat->reorder = true;
1.363 - parent_cat->serial++;
1.364 - }
1.365 - }
1.366 -
1.367 - /* The callbacks pointer is still valid, so fire away! */
1.368 - obj->callbacks->OnTopMenuObjectRemoved(this, object_id);
1.369 -
1.370 - /* Finally, mark the object as free. */
1.371 - obj->is_free = true;
1.372 -}
1.373 -
1.374 -bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position)
1.375 -{
1.376 - if (m_clients == NULL)
1.377 - {
1.378 - return false;
1.379 - }
1.380 -
1.381 - IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
1.382 - if (!pPlayer->IsInGame())
1.383 - {
1.384 - return false;
1.385 - }
1.386 -
1.387 - UpdateClientRoot(client, pPlayer);
1.388 -
1.389 - topmenu_player_t *pClient = &m_clients[client];
1.390 - if (pClient->root == NULL)
1.391 - {
1.392 - return false;
1.393 - }
1.394 -
1.395 - bool return_value = false;
1.396 -
1.397 - if (position == TopMenuPosition_LastCategory &&
1.398 - pClient->last_category < m_Categories.size())
1.399 - {
1.400 - return_value = DisplayCategory(client, pClient->last_category, hold_time, true);
1.401 - if (!return_value)
1.402 - {
1.403 - return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
1.404 - }
1.405 - }
1.406 - else if (position == TopMenuPosition_LastRoot)
1.407 - {
1.408 - pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
1.409 - }
1.410 - else if (position == TopMenuPosition_Start)
1.411 - {
1.412 - pClient->last_position = 0;
1.413 - pClient->last_category = 0;
1.414 - return_value = pClient->root->Display(client, hold_time);
1.415 - }
1.416 -
1.417 - return return_value;
1.418 -}
1.419 -
1.420 -bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position)
1.421 -{
1.422 - UpdateClientCategory(client, category);
1.423 -
1.424 - topmenu_player_t *pClient = &m_clients[client];
1.425 - if (category >= pClient->cat_count
1.426 - || pClient->cats[category].menu == NULL)
1.427 - {
1.428 - return false;
1.429 - }
1.430 -
1.431 - bool return_value = false;
1.432 -
1.433 - topmenu_player_category_t *player_cat = &(pClient->cats[category]);
1.434 -
1.435 - pClient->last_category = category;
1.436 - if (last_position)
1.437 - {
1.438 - return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position);
1.439 - }
1.440 - else
1.441 - {
1.442 - return_value = player_cat->menu->Display(client, hold_time);
1.443 - }
1.444 -
1.445 - return return_value;
1.446 -}
1.447 -
1.448 -void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
1.449 -{
1.450 - const char *item_name = menu->GetItemInfo(item, NULL);
1.451 - if (!item_name)
1.452 - {
1.453 - return;
1.454 - }
1.455 -
1.456 - topmenu_object_t *obj;
1.457 - topmenu_player_t *pClient = &m_clients[client];
1.458 - topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
1.459 - if (pObject == NULL)
1.460 - {
1.461 - return;
1.462 - }
1.463 -
1.464 - obj = *pObject;
1.465 -
1.466 - /* We now have the object... what do we do with it? */
1.467 - if (obj->type == TopMenuObject_Category)
1.468 - {
1.469 - /* If it's a category, the user wants to view it.. */
1.470 - for (size_t i = 0; i < m_Categories.size(); i++)
1.471 - {
1.472 - if (m_Categories[i]->obj == obj)
1.473 - {
1.474 - pClient->last_root_pos = item_on_page;
1.475 - if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false))
1.476 - {
1.477 - /* If we can't display the category, re-display the root menu */
1.478 - DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot);
1.479 - }
1.480 - break;
1.481 - }
1.482 - }
1.483 - }
1.484 - else
1.485 - {
1.486 - pClient->last_position = item_on_page;
1.487 -
1.488 - /* Re-check access in case this user had their credentials revoked */
1.489 - if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
1.490 - {
1.491 - DisplayMenu(client, 0, TopMenuPosition_LastCategory);
1.492 - return;
1.493 - }
1.494 -
1.495 - /* Pass the information on to the callback */
1.496 - obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id);
1.497 - }
1.498 -}
1.499 -
1.500 -void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
1.501 -{
1.502 - const char *item_name = menu->GetItemInfo(item, NULL);
1.503 - if (!item_name)
1.504 - {
1.505 - return;
1.506 - }
1.507 -
1.508 - topmenu_object_t *obj;
1.509 - topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
1.510 - if (pObject == NULL)
1.511 - {
1.512 - return;
1.513 - }
1.514 -
1.515 - obj = *pObject;
1.516 -
1.517 - style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id);
1.518 - if (style != ITEMDRAW_DEFAULT)
1.519 - {
1.520 - return;
1.521 - }
1.522 -
1.523 - if (obj->cmdname[0] == '\0')
1.524 - {
1.525 - return;
1.526 - }
1.527 -
1.528 - if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
1.529 - {
1.530 - style = ITEMDRAW_IGNORE;
1.531 - }
1.532 -}
1.533 -
1.534 -unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu,
1.535 - int client,
1.536 - IMenuPanel *panel,
1.537 - unsigned int item,
1.538 - const ItemDrawInfo &dr)
1.539 -{
1.540 - const char *item_name = menu->GetItemInfo(item, NULL);
1.541 - if (!item_name)
1.542 - {
1.543 - return 0;
1.544 - }
1.545 -
1.546 - topmenu_object_t *obj;
1.547 - topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
1.548 - if (pObject == NULL)
1.549 - {
1.550 - return 0;
1.551 - }
1.552 -
1.553 - obj = *pObject;
1.554 -
1.555 - /* Ask the object to render the text for this client */
1.556 - char renderbuf[64];
1.557 - obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf));
1.558 -
1.559 - /* Build the new draw info */
1.560 - ItemDrawInfo new_dr = dr;
1.561 - new_dr.display = renderbuf;
1.562 -
1.563 - /* Man I love the menu API. Ask the panel to draw the item and give the position
1.564 - * back to Core's renderer. This way we don't have to worry about keeping the
1.565 - * render buffer static!
1.566 - */
1.567 - return panel->DrawItem(new_dr);
1.568 -}
1.569 -
1.570 -void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
1.571 -{
1.572 - if (reason == MenuCancel_ExitBack)
1.573 - {
1.574 - /* If the client chose exit back, they were on a category menu, and we can
1.575 - * now display the root menu from the last known position.
1.576 - */
1.577 - DisplayMenu(client, 0, TopMenuPosition_LastRoot);
1.578 - }
1.579 -}
1.580 -
1.581 -void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer)
1.582 -{
1.583 - topmenu_player_t *pClient = &m_clients[client];
1.584 - IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client);
1.585 -
1.586 - /* Determine if an update is necessary */
1.587 - bool is_update_needed = false;
1.588 - if (pClient->menu_serial != m_SerialNo)
1.589 - {
1.590 - is_update_needed = true;
1.591 - }
1.592 - else if (pPlayer->GetUserId() != pClient->user_id)
1.593 - {
1.594 - is_update_needed = true;
1.595 - }
1.596 -
1.597 - /* If no update is needed at the root level, just leave now */
1.598 - if (!is_update_needed)
1.599 - {
1.600 - return;
1.601 - }
1.602 -
1.603 - /* First we need to flush the cache... */
1.604 - TearDownClient(pClient);
1.605 -
1.606 - /* Now, rebuild the category list, but don't create menus */
1.607 - if (m_Categories.size() == 0)
1.608 - {
1.609 - pClient->cat_count = 0;
1.610 - pClient->cats = NULL;
1.611 - }
1.612 - else
1.613 - {
1.614 - pClient->cat_count = (unsigned int)m_Categories.size();
1.615 - pClient->cats = new topmenu_player_category_t[pClient->cat_count];
1.616 - memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count);
1.617 - }
1.618 -
1.619 - /* Re-sort the root categories if needed */
1.620 - SortCategoriesIfNeeded();
1.621 -
1.622 - /* Build the root menu */
1.623 - IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
1.624 -
1.625 - /* Add the sorted items */
1.626 - for (size_t i = 0; i < m_SortedCats.size(); i++)
1.627 - {
1.628 - if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0)
1.629 - {
1.630 - continue;
1.631 - }
1.632 - root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo(""));
1.633 - }
1.634 -
1.635 - /* Now we need to handle un-sorted items. This is slightly trickier, as we need to
1.636 - * pre-render each category name, and cache those names. Phew!
1.637 - */
1.638 - if (m_UnsortedCats.size())
1.639 - {
1.640 - obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()];
1.641 - for (size_t i = 0; i < m_UnsortedCats.size(); i++)
1.642 - {
1.643 - obj_by_name_t *temp_obj = &item_list[i];
1.644 - topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj;
1.645 - obj->callbacks->OnTopMenuDisplayOption(this,
1.646 - client,
1.647 - obj->object_id,
1.648 - temp_obj->name,
1.649 - sizeof(temp_obj->name));
1.650 - temp_obj->obj_index = m_UnsortedCats[i];
1.651 - }
1.652 -
1.653 - /* Sort our temp list */
1.654 - qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
1.655 -
1.656 - /* Add the new sorted categories */
1.657 - for (size_t i = 0; i < m_UnsortedCats.size(); i++)
1.658 - {
1.659 - if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0)
1.660 - {
1.661 - continue;
1.662 - }
1.663 - root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo(""));
1.664 - }
1.665 -
1.666 - delete [] item_list;
1.667 - }
1.668 -
1.669 - /* Set the menu's title */
1.670 - char renderbuf[128];
1.671 - m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf));
1.672 - root_menu->SetDefaultTitle(renderbuf);
1.673 -
1.674 - /* The client is now fully updated */
1.675 - pClient->root = root_menu;
1.676 - pClient->user_id = pPlayer->GetUserId();
1.677 - pClient->menu_serial = m_SerialNo;
1.678 - pClient->last_position = 0;
1.679 - pClient->last_category = 0;
1.680 - pClient->last_root_pos = 0;
1.681 -}
1.682 -
1.683 -void TopMenu::UpdateClientCategory(int client, unsigned int category)
1.684 -{
1.685 - /* Update the client's root menu just in case it needs it. This
1.686 - * will validate that we have both a valid client and a valid
1.687 - * category structure for that client.
1.688 - */
1.689 - UpdateClientRoot(client);
1.690 -
1.691 - /* Now it's guaranteed that our category tables will be usable */
1.692 - topmenu_player_t *pClient = &m_clients[client];
1.693 - topmenu_category_t *cat = m_Categories[category];
1.694 - topmenu_player_category_t *player_cat = &(pClient->cats[category]);
1.695 -
1.696 - /* Does the category actually need updating? */
1.697 - if (player_cat->serial == cat->serial)
1.698 - {
1.699 - return;
1.700 - }
1.701 -
1.702 - /* Destroy any existing menu */
1.703 - if (player_cat->menu)
1.704 - {
1.705 - player_cat->menu->Destroy();
1.706 - player_cat->menu = NULL;
1.707 - }
1.708 -
1.709 - if (pClient->last_category == category)
1.710 - {
1.711 - pClient->last_position = 0;
1.712 - }
1.713 -
1.714 - IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
1.715 -
1.716 - /* Categories get an "exit back" button */
1.717 - cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK);
1.718 -
1.719 - /* Re-sort the category if needed */
1.720 - SortCategoryIfNeeded(category);
1.721 -
1.722 - /* Build the menu with the sorted items first */
1.723 - for (size_t i = 0; i < cat->sorted.size(); i++)
1.724 - {
1.725 - cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo(""));
1.726 - }
1.727 -
1.728 - /* Now handle unsorted items */
1.729 - if (cat->unsorted.size())
1.730 - {
1.731 - /* Build a list of the item names */
1.732 - obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()];
1.733 - for (size_t i = 0; i < cat->unsorted.size(); i++)
1.734 - {
1.735 - obj_by_name_t *item = &item_list[i];
1.736 - topmenu_object_t *obj = cat->unsorted[i];
1.737 - obj->callbacks->OnTopMenuDisplayOption(this,
1.738 - client,
1.739 - obj->object_id,
1.740 - item->name,
1.741 - sizeof(item->name));
1.742 - item->obj_index = (unsigned int)i;
1.743 - }
1.744 -
1.745 - /* Sort the names */
1.746 - qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
1.747 -
1.748 - /* Add to the menu */
1.749 - for (size_t i = 0; i < cat->unsorted.size(); i++)
1.750 - {
1.751 - cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo(""));
1.752 - }
1.753 -
1.754 - delete [] item_list;
1.755 - }
1.756 -
1.757 - /* Set the menu's title */
1.758 - char renderbuf[128];
1.759 - cat->obj->callbacks->OnTopMenuDisplayTitle(this,
1.760 - client,
1.761 - cat->obj->object_id,
1.762 - renderbuf,
1.763 - sizeof(renderbuf));
1.764 - cat_menu->SetDefaultTitle(renderbuf);
1.765 -
1.766 - /* We're done! */
1.767 - player_cat->menu = cat_menu;
1.768 - player_cat->serial = cat->serial;
1.769 -}
1.770 -
1.771 -void TopMenu::SortCategoryIfNeeded(unsigned int category)
1.772 -{
1.773 - topmenu_category_t *cat = m_Categories[category];
1.774 - if (!cat->reorder)
1.775 - {
1.776 - return;
1.777 - }
1.778 -
1.779 - cat->sorted.clear();
1.780 - cat->unsorted.clear();
1.781 -
1.782 - if (cat->obj_list.size() == 0)
1.783 - {
1.784 - cat->reorder = false;
1.785 - return;
1.786 - }
1.787 -
1.788 - CVector<unsigned int> to_sort;
1.789 - for (size_t i = 0; i < cat->obj_list.size(); i++)
1.790 - {
1.791 - to_sort.push_back(i);
1.792 - }
1.793 -
1.794 - /* Find a matching category in the configs */
1.795 - config_category_t *config_cat = NULL;
1.796 - for (size_t i = 0; i < m_Config.cats.size(); i++)
1.797 - {
1.798 - if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0)
1.799 - {
1.800 - config_cat = m_Config.cats[i];
1.801 - break;
1.802 - }
1.803 - }
1.804 -
1.805 - /* If there is a matching category, build a pre-sorted item list */
1.806 - if (config_cat != NULL)
1.807 - {
1.808 - /* Find matching objects in this category */
1.809 - for (size_t i = 0; i < config_cat->commands.size(); i++)
1.810 - {
1.811 - const char *config_name = m_Config.strings.GetString(config_cat->commands[i]);
1.812 - for (size_t j = 0; j < to_sort.size(); j++)
1.813 - {
1.814 - if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0)
1.815 - {
1.816 - /* Place in the final list, then remove from the temporary list */
1.817 - cat->sorted.push_back(cat->obj_list[to_sort[j]]);
1.818 - to_sort.erase(to_sort.iterAt(j));
1.819 - break;
1.820 - }
1.821 - }
1.822 - }
1.823 - }
1.824 -
1.825 - /* Push any remaining items onto the unsorted list */
1.826 - for (size_t i = 0; i < to_sort.size(); i++)
1.827 - {
1.828 - cat->unsorted.push_back(cat->obj_list[to_sort[i]]);
1.829 - }
1.830 -
1.831 - cat->reorder = false;
1.832 -}
1.833 -
1.834 -void TopMenu::SortCategoriesIfNeeded()
1.835 -{
1.836 - if (!m_bCatsNeedResort)
1.837 - {
1.838 - return;
1.839 - }
1.840 -
1.841 - /* Clear sort results */
1.842 - m_SortedCats.clear();
1.843 - m_UnsortedCats.clear();
1.844 -
1.845 - if (m_Categories.size() == 0)
1.846 - {
1.847 - m_bCatsNeedResort = false;
1.848 - return;
1.849 - }
1.850 -
1.851 - CVector<unsigned int> to_sort;
1.852 - for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++)
1.853 - {
1.854 - to_sort.push_back(i);
1.855 - }
1.856 -
1.857 - /* If we have any predefined categories, add them in as they appear. */
1.858 - for (size_t i= 0; i < m_Config.cats.size(); i++)
1.859 - {
1.860 - /* Find this category and map it in if we can */
1.861 - for (size_t j = 0; j < to_sort.size(); j++)
1.862 - {
1.863 - if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name),
1.864 - m_Categories[to_sort[j]]->obj->name) == 0)
1.865 - {
1.866 - /* Add to the real list and remove from the temporary */
1.867 - m_SortedCats.push_back(to_sort[j]);
1.868 - to_sort.erase(to_sort.iterAt(j));
1.869 - break;
1.870 - }
1.871 - }
1.872 - }
1.873 -
1.874 - /* Push any remaining items onto the unsorted list */
1.875 - for (size_t i = 0; i < to_sort.size(); i++)
1.876 - {
1.877 - m_UnsortedCats.push_back(to_sort[i]);
1.878 - }
1.879 -
1.880 - m_bCatsNeedResort = false;
1.881 -}
1.882 -
1.883 -void TopMenu::CreatePlayers(int max_clients)
1.884 -{
1.885 - m_max_clients = max_clients;
1.886 - m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1));
1.887 - memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1));
1.888 -}
1.889 -
1.890 -void TopMenu::TearDownClient(topmenu_player_t *player)
1.891 -{
1.892 - if (player->cats != NULL)
1.893 - {
1.894 - for (unsigned int i = 0; i < player->cat_count; i++)
1.895 - {
1.896 - topmenu_player_category_t *player_cat = &(player->cats[i]);
1.897 - if (player_cat->menu != NULL)
1.898 - {
1.899 - player_cat->menu->Destroy();
1.900 - }
1.901 - }
1.902 - delete [] player->cats;
1.903 - }
1.904 -
1.905 - if (player->root != NULL)
1.906 - {
1.907 - player->root->Destroy();
1.908 - }
1.909 -
1.910 - memset(player, 0, sizeof(topmenu_player_t));
1.911 -}
1.912 -
1.913 -bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength)
1.914 -{
1.915 - SMCError err;
1.916 - SMCStates states;
1.917 -
1.918 - if ((err = textparsers->ParseFile_SMC(file, this, &states))
1.919 - != SMCError_Okay)
1.920 - {
1.921 - const char *err_string = textparsers->GetSMCErrorString(err);
1.922 - if (!err_string)
1.923 - {
1.924 - err_string = "Unknown";
1.925 - }
1.926 -
1.927 - UTIL_Format(error, maxlength, "%s", err_string);
1.928 -
1.929 - return false;
1.930 - }
1.931 -
1.932 - return true;
1.933 -}
1.934 -
1.935 -bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner)
1.936 -{
1.937 - /* First sweep the categories owned by us */
1.938 - CVector<unsigned int> obj_list;
1.939 - for (size_t i = 0; i < m_Categories.size(); i++)
1.940 - {
1.941 - if (m_Categories[i]->obj->owner == owner)
1.942 - {
1.943 - obj_list.push_back(m_Categories[i]->obj->object_id);
1.944 - }
1.945 - }
1.946 -
1.947 - for (size_t i = 0; i < obj_list.size(); i++)
1.948 - {
1.949 - RemoveFromMenu(obj_list[i]);
1.950 - }
1.951 -
1.952 - /* Now we can look for actual items */
1.953 - for (size_t i = 0; i < m_Objects.size(); i++)
1.954 - {
1.955 - if (m_Objects[i]->is_free)
1.956 - {
1.957 - continue;
1.958 - }
1.959 - if (m_Objects[i]->owner == owner)
1.960 - {
1.961 - assert(m_Objects[i]->type != TopMenuObject_Category);
1.962 - RemoveFromMenu(m_Objects[i]->object_id);
1.963 - }
1.964 - }
1.965 -
1.966 - return true;
1.967 -}
1.968 -
1.969 -#define PARSE_STATE_NONE 0
1.970 -#define PARSE_STATE_MAIN 1
1.971 -#define PARSE_STATE_CATEGORY 2
1.972 -unsigned int ignore_parse_level = 0;
1.973 -unsigned int current_parse_state = 0;
1.974 -config_category_t *cur_cat = NULL;
1.975 -
1.976 -void TopMenu::ReadSMC_ParseStart()
1.977 -{
1.978 - current_parse_state = PARSE_STATE_NONE;
1.979 - ignore_parse_level = 0;
1.980 - cur_cat = NULL;
1.981 -
1.982 - /* Reset the old config */
1.983 - m_Config.strings.Reset();
1.984 - for (size_t i = 0; i < m_Config.cats.size(); i++)
1.985 - {
1.986 - delete m_Config.cats[i];
1.987 - }
1.988 - m_Config.cats.clear();
1.989 -}
1.990 -
1.991 -SMCResult TopMenu::ReadSMC_NewSection(const SMCStates *states, const char *name)
1.992 -{
1.993 - if (ignore_parse_level)
1.994 - {
1.995 - ignore_parse_level++;
1.996 - }
1.997 - else
1.998 - {
1.999 - if (current_parse_state == PARSE_STATE_NONE)
1.1000 - {
1.1001 - if (strcmp(name, "Menu") == 0)
1.1002 - {
1.1003 - current_parse_state = PARSE_STATE_MAIN;
1.1004 - }
1.1005 - else
1.1006 - {
1.1007 - ignore_parse_level = 1;
1.1008 - }
1.1009 - }
1.1010 - else if (current_parse_state == PARSE_STATE_MAIN)
1.1011 - {
1.1012 - cur_cat = new config_category_t;
1.1013 - cur_cat->name = m_Config.strings.AddString(name);
1.1014 - m_Config.cats.push_back(cur_cat);
1.1015 - current_parse_state = PARSE_STATE_CATEGORY;
1.1016 - }
1.1017 - else
1.1018 - {
1.1019 - ignore_parse_level = 1;
1.1020 - }
1.1021 - }
1.1022 -
1.1023 - return SMCResult_Continue;
1.1024 -}
1.1025 -
1.1026 -SMCResult TopMenu::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
1.1027 -{
1.1028 - if (ignore_parse_level > 0
1.1029 - || current_parse_state != PARSE_STATE_CATEGORY
1.1030 - || cur_cat == NULL)
1.1031 - {
1.1032 - return SMCResult_Continue;
1.1033 - }
1.1034 -
1.1035 - if (strcmp(key, "item") == 0)
1.1036 - {
1.1037 - cur_cat->commands.push_back(m_Config.strings.AddString(value));
1.1038 - }
1.1039 -
1.1040 - return SMCResult_Continue;
1.1041 -}
1.1042 -
1.1043 -SMCResult TopMenu::ReadSMC_LeavingSection(const SMCStates *states)
1.1044 -{
1.1045 - if (ignore_parse_level)
1.1046 - {
1.1047 - ignore_parse_level--;
1.1048 - }
1.1049 - else
1.1050 - {
1.1051 - if (current_parse_state == PARSE_STATE_CATEGORY)
1.1052 - {
1.1053 - cur_cat = NULL;
1.1054 - current_parse_state = PARSE_STATE_MAIN;
1.1055 - }
1.1056 - else if (current_parse_state == PARSE_STATE_MAIN)
1.1057 - {
1.1058 - current_parse_state = PARSE_STATE_NONE;
1.1059 - }
1.1060 - }
1.1061 -
1.1062 - return SMCResult_Continue;
1.1063 -}
1.1064 -
1.1065 -unsigned int TopMenu::FindCategory(const char *name)
1.1066 -{
1.1067 - topmenu_object_t **p_obj = m_ObjLookup.retrieve(name);
1.1068 - if (!p_obj)
1.1069 - {
1.1070 - return 0;
1.1071 - }
1.1072 -
1.1073 - topmenu_object_t *obj = *p_obj;
1.1074 - if (obj->type != TopMenuObject_Category)
1.1075 - {
1.1076 - return 0;
1.1077 - }
1.1078 -
1.1079 - return obj->object_id;
1.1080 -}
1.1081 -
1.1082 -int _SortObjectNamesDescending(const void *ptr1, const void *ptr2)
1.1083 -{
1.1084 - obj_by_name_t *obj1 = (obj_by_name_t *)ptr1;
1.1085 - obj_by_name_t *obj2 = (obj_by_name_t *)ptr2;
1.1086 - return strcmp(obj1->name, obj2->name);
1.1087 -}
1.1088 -
1.1089 -unsigned int strncopy(char *dest, const char *src, size_t count)
1.1090 -{
1.1091 - if (!count)
1.1092 - {
1.1093 - return 0;
1.1094 - }
1.1095 -
1.1096 - char *start = dest;
1.1097 - while ((*src) && (--count))
1.1098 - {
1.1099 - *dest++ = *src++;
1.1100 - }
1.1101 - *dest = '\0';
1.1102 -
1.1103 - return (dest - start);
1.1104 -}
1.1105 -
1.1106 -size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
1.1107 -{
1.1108 - va_list ap;
1.1109 - va_start(ap, fmt);
1.1110 - size_t len = vsnprintf(buffer, maxlength, fmt, ap);
1.1111 - va_end(ap);
1.1112 -
1.1113 - if (len >= maxlength)
1.1114 - {
1.1115 - buffer[maxlength - 1] = '\0';
1.1116 - return (maxlength - 1);
1.1117 - }
1.1118 - else
1.1119 - {
1.1120 - return len;
1.1121 - }
1.1122 -}