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