1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/extensions/topmenus/TopMenu.cpp Wed Sep 26 14:38:40 2007 +0000
1.3 @@ -0,0 +1,942 @@
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: extension.cpp 1336 2007-08-15 06:19:30Z damagedsoul $
1.33 + */
1.34 +
1.35 +#include <stdlib.h>
1.36 +#include "TopMenu.h"
1.37 +
1.38 +struct obj_by_name_t
1.39 +{
1.40 + unsigned int obj_index;
1.41 + char name[64];
1.42 +};
1.43 +
1.44 +int _SortObjectNamesDescending(const void *ptr1, const void *ptr2);
1.45 +unsigned int strncopy(char *dest, const char *src, size_t count);
1.46 +size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
1.47 +
1.48 +TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks)
1.49 +{
1.50 + m_clients = NULL;
1.51 + m_SerialNo = 1;
1.52 + m_pTitle = callbacks;
1.53 + m_max_clients = 0;
1.54 +
1.55 + if (playerhelpers->IsServerActivated())
1.56 + {
1.57 + CreatePlayers(playerhelpers->GetMaxClients());
1.58 + }
1.59 +}
1.60 +
1.61 +TopMenu::~TopMenu()
1.62 +{
1.63 + /* Delete all categories */
1.64 + for (size_t i = 0; i < m_Categories.size(); i++)
1.65 + {
1.66 + delete m_Categories[i];
1.67 + }
1.68 +
1.69 + /* Delete all items */
1.70 + for (size_t i = 0; i < m_Objects.size(); i++)
1.71 + {
1.72 + delete m_Objects[i];
1.73 + }
1.74 +
1.75 + /* Delete all cached config entries */
1.76 + for (size_t i = 0; i < m_Config.cats.size(); i++)
1.77 + {
1.78 + delete m_Config.cats[i];
1.79 + }
1.80 +
1.81 + /* Sweep players */
1.82 + for (int i = 0; i <= m_max_clients; i++)
1.83 + {
1.84 + TearDownClient(&m_clients[i]);
1.85 + }
1.86 + delete [] m_clients;
1.87 +}
1.88 +
1.89 +void TopMenu::OnClientConnected(int client)
1.90 +{
1.91 + if (m_clients == NULL)
1.92 + {
1.93 + return;
1.94 + }
1.95 +
1.96 + topmenu_player_t *player = &m_clients[client];
1.97 + TearDownClient(player);
1.98 +}
1.99 +
1.100 +void TopMenu::OnClientDisconnected(int client)
1.101 +{
1.102 + if (m_clients == NULL)
1.103 + {
1.104 + return;
1.105 + }
1.106 +
1.107 + topmenu_player_t *player = &m_clients[client];
1.108 + TearDownClient(player);
1.109 +}
1.110 +
1.111 +void TopMenu::OnServerActivated(int max_clients)
1.112 +{
1.113 + if (m_clients == NULL)
1.114 + {
1.115 + CreatePlayers(max_clients);
1.116 + }
1.117 +}
1.118 +
1.119 +unsigned int TopMenu::AddToMenu(const char *name,
1.120 + TopMenuObjectType type,
1.121 + ITopMenuObjectCallbacks *callbacks,
1.122 + IdentityToken_t *owner,
1.123 + const char *cmdname,
1.124 + FlagBits flags,
1.125 + unsigned int parent)
1.126 +{
1.127 + /* Sanity checks */
1.128 + if (type == TopMenuObject_Category && parent != 0)
1.129 + {
1.130 + return 0;
1.131 + }
1.132 + else if (type == TopMenuObject_Item && parent == 0)
1.133 + {
1.134 + return 0;
1.135 + }
1.136 + else if (m_ObjLookup.retrieve(name) != NULL)
1.137 + {
1.138 + return 0;
1.139 + }
1.140 +
1.141 + /* If we're adding an item, make sure the parent is valid,
1.142 + * and that the parent is a category.
1.143 + */
1.144 + topmenu_object_t *parent_obj = NULL;
1.145 + topmenu_category_t *parent_cat = NULL;
1.146 + if (type == TopMenuObject_Item)
1.147 + {
1.148 + /* Check parent index. Note it will be >= 1 here. */
1.149 + if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free)
1.150 + {
1.151 + return 0;
1.152 + }
1.153 + parent_obj = m_Objects[parent - 1];
1.154 +
1.155 + /* Find an equivalent pointer in the category array. */
1.156 + for (size_t i = 0; i < m_Categories.size(); i++)
1.157 + {
1.158 + if (m_Categories[i]->obj == parent_obj)
1.159 + {
1.160 + parent_cat = m_Categories[i];
1.161 + break;
1.162 + }
1.163 + }
1.164 +
1.165 + /* If none was found, leave. */
1.166 + if (parent_cat == NULL)
1.167 + {
1.168 + return 0;
1.169 + }
1.170 + }
1.171 +
1.172 + /* Re-use an old object pointer if we can. */
1.173 + topmenu_object_t *obj = NULL;
1.174 + for (size_t i = 0; i < m_Objects.size(); i++)
1.175 + {
1.176 + if (m_Objects[i]->is_free == true)
1.177 + {
1.178 + obj = m_Objects[i];
1.179 + break;
1.180 + }
1.181 + }
1.182 +
1.183 + /* Otherwise, allocate a new one. */
1.184 + if (obj == NULL)
1.185 + {
1.186 + obj = new topmenu_object_t;
1.187 + obj->object_id = ((unsigned int)m_Objects.size()) + 1;
1.188 + m_Objects.push_back(obj);
1.189 + }
1.190 +
1.191 + /* Initialize the object's properties. */
1.192 + obj->callbacks = callbacks;
1.193 + obj->flags = flags;
1.194 + obj->owner = owner;
1.195 + obj->type = type;
1.196 + obj->is_free = false;
1.197 + obj->parent = parent_obj;
1.198 + strncopy(obj->name, name, sizeof(obj->name));
1.199 + strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname));
1.200 +
1.201 + if (obj->type == TopMenuObject_Category)
1.202 + {
1.203 + /* Create a new category entry */
1.204 + topmenu_category_t *cat = new topmenu_category_t;
1.205 + cat->obj = obj;
1.206 + cat->reorder = false;
1.207 + cat->serial = 1;
1.208 +
1.209 + /* Add it, then update our serial change number. */
1.210 + m_Categories.push_back(cat);
1.211 + m_SerialNo++;
1.212 +
1.213 + /* Updating sorting info */
1.214 + m_bCatsNeedResort = true;
1.215 + }
1.216 + else if (obj->type == TopMenuObject_Item)
1.217 + {
1.218 + /* Update the category, mark it as needing changes */
1.219 + parent_cat->obj_list.push_back(obj);
1.220 + parent_cat->reorder = true;
1.221 + parent_cat->serial++;
1.222 + }
1.223 +
1.224 + m_ObjLookup.insert(name, obj);
1.225 +
1.226 + return obj->object_id;
1.227 +}
1.228 +
1.229 +void TopMenu::RemoveFromMenu(unsigned int object_id)
1.230 +{
1.231 + if (object_id == 0
1.232 + || object_id > m_Objects.size()
1.233 + || m_Objects[object_id - 1]->is_free)
1.234 + {
1.235 + return;
1.236 + }
1.237 +
1.238 + topmenu_object_t *obj = m_Objects[object_id - 1];
1.239 +
1.240 + m_ObjLookup.remove(obj->name);
1.241 +
1.242 + if (obj->type == TopMenuObject_Category)
1.243 + {
1.244 + /* Find it in the category list */
1.245 + for (size_t i = 0; i < m_Categories.size(); i++)
1.246 + {
1.247 + if (m_Categories[i]->obj == obj)
1.248 + {
1.249 + /* Erase from here */
1.250 + delete m_Categories[i];
1.251 + m_Categories.erase(m_Categories.iterAt(i));
1.252 + break;
1.253 + }
1.254 + }
1.255 + /* Update us as changed */
1.256 + m_SerialNo++;
1.257 + m_bCatsNeedResort = true;
1.258 + }
1.259 + else if (obj->type == TopMenuObject_Item)
1.260 + {
1.261 + /* Find the category this item is in. */
1.262 + topmenu_category_t *parent_cat = NULL;
1.263 + for (size_t i = 0; i < m_Categories.size(); i++)
1.264 + {
1.265 + if (m_Categories[i]->obj == obj->parent)
1.266 + {
1.267 + parent_cat = m_Categories[i];
1.268 + break;
1.269 + }
1.270 + }
1.271 +
1.272 + /* Erase it from the category's lists. */
1.273 + if (parent_cat)
1.274 + {
1.275 + for (size_t i = 0; i < parent_cat->obj_list.size(); i++)
1.276 + {
1.277 + if (parent_cat->obj_list[i] == obj)
1.278 + {
1.279 + parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i));
1.280 + break;
1.281 + }
1.282 + }
1.283 + parent_cat->reorder = true;
1.284 + parent_cat->serial++;
1.285 + }
1.286 + }
1.287 +
1.288 + /* Finally, mark the object as free. */
1.289 + obj->is_free = true;
1.290 +}
1.291 +
1.292 +bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position)
1.293 +{
1.294 + if (m_clients == NULL)
1.295 + {
1.296 + return false;
1.297 + }
1.298 +
1.299 + IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
1.300 + if (!pPlayer->IsInGame())
1.301 + {
1.302 + return false;
1.303 + }
1.304 +
1.305 + UpdateClientRoot(client, pPlayer);
1.306 +
1.307 + topmenu_player_t *pClient = &m_clients[client];
1.308 + if (pClient->root == NULL)
1.309 + {
1.310 + return false;
1.311 + }
1.312 +
1.313 + bool return_value = false;
1.314 +
1.315 + if (position == TopMenuPosition_LastCategory &&
1.316 + pClient->last_category < m_Categories.size())
1.317 + {
1.318 + return_value = DisplayCategory(client, pClient->last_category, hold_time, true);
1.319 + }
1.320 + else if (position == TopMenuPosition_LastRoot)
1.321 + {
1.322 + pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
1.323 + }
1.324 + else if (position == TopMenuPosition_Start)
1.325 + {
1.326 + pClient->last_position = 0;
1.327 + pClient->last_category = 0;
1.328 + return_value = pClient->root->Display(client, hold_time);
1.329 + }
1.330 +
1.331 + return return_value;
1.332 +}
1.333 +
1.334 +bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position)
1.335 +{
1.336 + UpdateClientCategory(client, category);
1.337 +
1.338 + topmenu_player_t *pClient = &m_clients[client];
1.339 + if (category >= pClient->cat_count
1.340 + || pClient->cats[category].menu == NULL)
1.341 + {
1.342 + return false;
1.343 + }
1.344 +
1.345 + bool return_value = false;
1.346 +
1.347 + topmenu_player_category_t *player_cat = &(pClient->cats[category]);
1.348 +
1.349 + pClient->last_category = category;
1.350 + if (last_position)
1.351 + {
1.352 + return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position);
1.353 + }
1.354 + else
1.355 + {
1.356 + return_value = player_cat->menu->Display(client, hold_time);
1.357 + }
1.358 +
1.359 + return return_value;
1.360 +}
1.361 +
1.362 +void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
1.363 +{
1.364 + const char *item_name = menu->GetItemInfo(item, NULL);
1.365 + if (!item_name)
1.366 + {
1.367 + return;
1.368 + }
1.369 +
1.370 + topmenu_object_t *obj;
1.371 + topmenu_player_t *pClient = &m_clients[client];
1.372 + topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
1.373 + if (pObject == NULL)
1.374 + {
1.375 + return;
1.376 + }
1.377 +
1.378 + obj = *pObject;
1.379 +
1.380 + /* We now have the object... what do we do with it? */
1.381 + if (obj->type == TopMenuObject_Category)
1.382 + {
1.383 + /* If it's a category, the user wants to view it.. */
1.384 + for (size_t i = 0; i < m_Categories.size(); i++)
1.385 + {
1.386 + if (m_Categories[i]->obj == obj)
1.387 + {
1.388 + pClient->last_root_pos = item_on_page;
1.389 + DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false);
1.390 + break;
1.391 + }
1.392 + }
1.393 + }
1.394 + else
1.395 + {
1.396 + pClient->last_position = item_on_page;
1.397 +
1.398 + /* Re-check access in case this user had their credentials revoked */
1.399 + if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
1.400 + {
1.401 + DisplayMenu(client, 0, TopMenuPosition_LastCategory);
1.402 + return;
1.403 + }
1.404 +
1.405 + /* Pass the information on to the callback */
1.406 + obj->callbacks->OnTopMenuSelectOption(client, obj->object_id);
1.407 + }
1.408 +}
1.409 +
1.410 +void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
1.411 +{
1.412 + const char *item_name = menu->GetItemInfo(item, NULL);
1.413 + if (!item_name)
1.414 + {
1.415 + return;
1.416 + }
1.417 +
1.418 + topmenu_object_t *obj;
1.419 + topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
1.420 + if (pObject == NULL)
1.421 + {
1.422 + return;
1.423 + }
1.424 +
1.425 + obj = *pObject;
1.426 +
1.427 + if (obj->cmdname[0] == '\0')
1.428 + {
1.429 + return;
1.430 + }
1.431 +
1.432 + if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
1.433 + {
1.434 + style = ITEMDRAW_IGNORE;
1.435 + }
1.436 +}
1.437 +
1.438 +unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu,
1.439 + int client,
1.440 + IMenuPanel *panel,
1.441 + unsigned int item,
1.442 + const ItemDrawInfo &dr)
1.443 +{
1.444 + const char *item_name = menu->GetItemInfo(item, NULL);
1.445 + if (!item_name)
1.446 + {
1.447 + return 0;
1.448 + }
1.449 +
1.450 + topmenu_object_t *obj;
1.451 + topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
1.452 + if (pObject == NULL)
1.453 + {
1.454 + return 0;
1.455 + }
1.456 +
1.457 + obj = *pObject;
1.458 +
1.459 + /* Ask the object to render the text for this client */
1.460 + char renderbuf[64];
1.461 + obj->callbacks->OnTopMenuDrawOption(client, obj->object_id, renderbuf, sizeof(renderbuf));
1.462 +
1.463 + /* Build the new draw info */
1.464 + ItemDrawInfo new_dr = dr;
1.465 + new_dr.display = renderbuf;
1.466 +
1.467 + /* Man I love the menu API. Ask the panel to draw the item and give the position
1.468 + * back to Core's renderer. This way we don't have to worry about keeping the
1.469 + * render buffer static!
1.470 + */
1.471 + return panel->DrawItem(new_dr);
1.472 +}
1.473 +
1.474 +void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
1.475 +{
1.476 + if (reason == MenuCancel_ExitBack)
1.477 + {
1.478 + /* If the client chose exit back, they were on a category menu, and we can
1.479 + * now display the root menu from the last known position.
1.480 + */
1.481 + DisplayMenu(client, 0, TopMenuPosition_LastRoot);
1.482 + }
1.483 +}
1.484 +
1.485 +void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer)
1.486 +{
1.487 + topmenu_player_t *pClient = &m_clients[client];
1.488 + IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client);
1.489 +
1.490 + /* Determine if an update is necessary */
1.491 + bool is_update_needed = false;
1.492 + if (pClient->menu_serial != m_SerialNo)
1.493 + {
1.494 + is_update_needed = true;
1.495 + }
1.496 + else if (pPlayer->GetUserId() != pClient->user_id)
1.497 + {
1.498 + is_update_needed = true;
1.499 + }
1.500 +
1.501 + /* If no update is needed at the root level, just leave now */
1.502 + if (!is_update_needed)
1.503 + {
1.504 + return;
1.505 + }
1.506 +
1.507 + /* First we need to flush the cache... */
1.508 + TearDownClient(pClient);
1.509 +
1.510 + /* Now, rebuild the category list, but don't create menus */
1.511 + if (m_Categories.size() == 0)
1.512 + {
1.513 + pClient->cat_count = 0;
1.514 + pClient->cats = NULL;
1.515 + }
1.516 + else
1.517 + {
1.518 + pClient->cat_count = (unsigned int)m_Categories.size();
1.519 + pClient->cats = new topmenu_player_category_t[pClient->cat_count];
1.520 + memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count);
1.521 + }
1.522 +
1.523 + /* Re-sort the root categories if needed */
1.524 + SortCategoriesIfNeeded();
1.525 +
1.526 + /* Build the root menu */
1.527 + IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
1.528 +
1.529 + /* Add the sorted items */
1.530 + for (size_t i = 0; i < m_SortedCats.size(); i++)
1.531 + {
1.532 + root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo(""));
1.533 + }
1.534 +
1.535 + /* Now we need to handle un-sorted items. This is slightly trickier, as we need to
1.536 + * pre-render each category name, and cache those names. Phew!
1.537 + */
1.538 + if (m_UnsortedCats.size())
1.539 + {
1.540 + obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()];
1.541 + for (size_t i = 0; i < m_UnsortedCats.size(); i++)
1.542 + {
1.543 + obj_by_name_t *temp_obj = &item_list[i];
1.544 + topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj;
1.545 + obj->callbacks->OnTopMenuDrawOption(client,
1.546 + obj->object_id,
1.547 + temp_obj->name,
1.548 + sizeof(temp_obj->name));
1.549 + temp_obj->obj_index = m_UnsortedCats[i];
1.550 + }
1.551 +
1.552 + /* Sort our temp list */
1.553 + qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
1.554 +
1.555 + /* Add the new sorted categories */
1.556 + for (size_t i = 0; i < m_SortedCats.size(); i++)
1.557 + {
1.558 + root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo(""));
1.559 + }
1.560 +
1.561 + delete [] item_list;
1.562 + }
1.563 +
1.564 + /* Set the menu's title */
1.565 + char renderbuf[128];
1.566 + m_pTitle->OnTopMenuDrawTitle(client, 0, renderbuf, sizeof(renderbuf));
1.567 + root_menu->SetDefaultTitle(renderbuf);
1.568 +
1.569 + /* The client is now fully updated */
1.570 + pClient->root = root_menu;
1.571 + pClient->user_id = pPlayer->GetUserId();
1.572 + pClient->menu_serial = m_SerialNo;
1.573 + pClient->last_position = 0;
1.574 + pClient->last_category = 0;
1.575 + pClient->last_root_pos = 0;
1.576 +}
1.577 +
1.578 +void TopMenu::UpdateClientCategory(int client, unsigned int category)
1.579 +{
1.580 + /* Update the client's root menu just in case it needs it. This
1.581 + * will validate that we have both a valid client and a valid
1.582 + * category structure for that client.
1.583 + */
1.584 + UpdateClientRoot(client);
1.585 +
1.586 + /* Now it's guaranteed that our category tables will be usable */
1.587 + topmenu_player_t *pClient = &m_clients[client];
1.588 + topmenu_category_t *cat = m_Categories[category];
1.589 + topmenu_player_category_t *player_cat = &(pClient->cats[category]);
1.590 +
1.591 + /* Does the category actually need updating? */
1.592 + if (player_cat->serial == cat->serial)
1.593 + {
1.594 + return;
1.595 + }
1.596 +
1.597 + /* Destroy any existing menu */
1.598 + if (player_cat->menu)
1.599 + {
1.600 + player_cat->menu->Destroy();
1.601 + player_cat->menu = NULL;
1.602 + }
1.603 +
1.604 + if (pClient->last_category == category)
1.605 + {
1.606 + pClient->last_position = 0;
1.607 + }
1.608 +
1.609 + IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
1.610 +
1.611 + /* Categories get an "exit back" button */
1.612 + cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK);
1.613 +
1.614 + /* Re-sort the category if needed */
1.615 + SortCategoryIfNeeded(category);
1.616 +
1.617 + /* Build the menu with the sorted items first */
1.618 + for (size_t i = 0; i < cat->sorted.size(); i++)
1.619 + {
1.620 + cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo(""));
1.621 + }
1.622 +
1.623 + /* Now handle unsorted items */
1.624 + if (cat->unsorted.size())
1.625 + {
1.626 + /* Build a list of the item names */
1.627 + obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()];
1.628 + for (size_t i = 0; i < cat->unsorted.size(); i++)
1.629 + {
1.630 + obj_by_name_t *item = &item_list[i];
1.631 + topmenu_object_t *obj = cat->unsorted[i];
1.632 + obj->callbacks->OnTopMenuDrawOption(client,
1.633 + obj->object_id,
1.634 + item->name,
1.635 + sizeof(item->name));
1.636 + item->obj_index = (unsigned int)i;
1.637 + }
1.638 +
1.639 + /* Sort the names */
1.640 + qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
1.641 +
1.642 + /* Add to the menu */
1.643 + for (size_t i = 0; i < cat->unsorted.size(); i++)
1.644 + {
1.645 + cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo(""));
1.646 + }
1.647 +
1.648 + delete [] item_list;
1.649 + }
1.650 +
1.651 + /* Set the menu's title */
1.652 + char renderbuf[128];
1.653 + cat->obj->callbacks->OnTopMenuDrawTitle(client, cat->obj->object_id, renderbuf, sizeof(renderbuf));
1.654 + cat_menu->SetDefaultTitle(renderbuf);
1.655 +
1.656 + /* We're done! */
1.657 + player_cat->menu = cat_menu;
1.658 + player_cat->serial = cat->serial;
1.659 +}
1.660 +
1.661 +void TopMenu::SortCategoryIfNeeded(unsigned int category)
1.662 +{
1.663 + topmenu_category_t *cat = m_Categories[category];
1.664 + if (!cat->reorder)
1.665 + {
1.666 + return;
1.667 + }
1.668 +
1.669 + cat->sorted.clear();
1.670 + cat->unsorted.clear();
1.671 +
1.672 + if (cat->obj_list.size() == 0)
1.673 + {
1.674 + cat->reorder = false;
1.675 + return;
1.676 + }
1.677 +
1.678 + CVector<unsigned int> to_sort;
1.679 + for (size_t i = 0; i < cat->obj_list.size(); i++)
1.680 + {
1.681 + to_sort.push_back(i);
1.682 + }
1.683 +
1.684 + /* Find a matching category in the configs */
1.685 + config_category_t *config_cat = NULL;
1.686 + for (size_t i = 0; i < m_Config.cats.size(); i++)
1.687 + {
1.688 + if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0)
1.689 + {
1.690 + config_cat = m_Config.cats[i];
1.691 + break;
1.692 + }
1.693 + }
1.694 +
1.695 + /* If there is a matching category, build a pre-sorted item list */
1.696 + if (config_cat != NULL)
1.697 + {
1.698 + /* Find matching objects in this category */
1.699 + for (size_t i = 0; i < config_cat->commands.size(); i++)
1.700 + {
1.701 + const char *config_name = m_Config.strings.GetString(config_cat->commands[i]);
1.702 + for (size_t j = 0; j < to_sort.size(); j++)
1.703 + {
1.704 + if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0)
1.705 + {
1.706 + /* Place in the final list, then remove from the temporary list */
1.707 + cat->sorted.push_back(cat->obj_list[to_sort[j]]);
1.708 + to_sort.erase(to_sort.iterAt(j));
1.709 + break;
1.710 + }
1.711 + }
1.712 + }
1.713 + }
1.714 +
1.715 + /* Push any remaining items onto the unsorted list */
1.716 + for (size_t i = 0; i < to_sort.size(); i++)
1.717 + {
1.718 + cat->unsorted.push_back(cat->obj_list[to_sort[i]]);
1.719 + }
1.720 +
1.721 + cat->reorder = false;
1.722 +}
1.723 +
1.724 +void TopMenu::SortCategoriesIfNeeded()
1.725 +{
1.726 + if (!m_bCatsNeedResort)
1.727 + {
1.728 + return;
1.729 + }
1.730 +
1.731 + /* Clear sort results */
1.732 + m_SortedCats.clear();
1.733 + m_UnsortedCats.clear();
1.734 +
1.735 + if (m_Categories.size() == 0)
1.736 + {
1.737 + m_bCatsNeedResort = false;
1.738 + return;
1.739 + }
1.740 +
1.741 + CVector<unsigned int> to_sort;
1.742 + for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++)
1.743 + {
1.744 + to_sort.push_back(i);
1.745 + }
1.746 +
1.747 + /* If we have any predefined categories, add them in as they appear. */
1.748 + for (size_t i= 0; i < m_Config.cats.size(); i++)
1.749 + {
1.750 + /* Find this category and map it in if we can */
1.751 + for (size_t j = 0; j < to_sort.size(); j++)
1.752 + {
1.753 + if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name),
1.754 + m_Categories[to_sort[j]]->obj->name) == 0)
1.755 + {
1.756 + /* Add to the real list and remove from the temporary */
1.757 + m_SortedCats.push_back(to_sort[j]);
1.758 + to_sort.erase(to_sort.iterAt(j));
1.759 + break;
1.760 + }
1.761 + }
1.762 + }
1.763 +
1.764 + /* Push any remaining items onto the unsorted list */
1.765 + for (size_t i = 0; i < to_sort.size(); i++)
1.766 + {
1.767 + m_UnsortedCats.push_back(to_sort[i]);
1.768 + }
1.769 +
1.770 + m_bCatsNeedResort = false;
1.771 +}
1.772 +
1.773 +void TopMenu::CreatePlayers(int max_clients)
1.774 +{
1.775 + m_max_clients = max_clients;
1.776 + m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1));
1.777 + memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1));
1.778 +}
1.779 +
1.780 +void TopMenu::TearDownClient(topmenu_player_t *player)
1.781 +{
1.782 + if (player->cats != NULL)
1.783 + {
1.784 + for (unsigned int i = 0; i < player->cat_count; i++)
1.785 + {
1.786 + topmenu_player_category_t *player_cat = &(player->cats[i]);
1.787 + if (player_cat->menu != NULL)
1.788 + {
1.789 + player_cat->menu->Destroy();
1.790 + }
1.791 + }
1.792 + delete [] player->cats;
1.793 + }
1.794 +
1.795 + if (player->root != NULL)
1.796 + {
1.797 + player->root->Destroy();
1.798 + }
1.799 +
1.800 + memset(player, 0, sizeof(topmenu_player_t));
1.801 +}
1.802 +
1.803 +bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength)
1.804 +{
1.805 + SMCParseError err;
1.806 + unsigned int line = 0, col = 0;
1.807 +
1.808 + if ((err = textparsers->ParseFile_SMC(file, this, &line, &col))
1.809 + != SMCParse_Okay)
1.810 + {
1.811 + const char *err_string = textparsers->GetSMCErrorString(err);
1.812 + if (!err_string)
1.813 + {
1.814 + err_string = "Unknown";
1.815 + }
1.816 +
1.817 + UTIL_Format(error, maxlength, "%s", err_string);
1.818 +
1.819 + return false;
1.820 + }
1.821 +
1.822 + return true;
1.823 +}
1.824 +
1.825 +#define PARSE_STATE_NONE 0
1.826 +#define PARSE_STATE_CATEGORY 1
1.827 +unsigned int ignore_parse_level = 0;
1.828 +unsigned int current_parse_state = 0;
1.829 +config_category_t *cur_cat = NULL;
1.830 +
1.831 +void TopMenu::ReadSMC_ParseStart()
1.832 +{
1.833 + current_parse_state = PARSE_STATE_NONE;
1.834 + ignore_parse_level = 0;
1.835 + cur_cat = NULL;
1.836 +
1.837 + /* Reset the old config */
1.838 + m_Config.strings.Reset();
1.839 + for (size_t i = 0; i < m_Config.cats.size(); i++)
1.840 + {
1.841 + delete m_Config.cats[i];
1.842 + }
1.843 + m_Config.cats.clear();
1.844 +}
1.845 +
1.846 +SMCParseResult TopMenu::ReadSMC_NewSection(const char *name, bool opt_quotes)
1.847 +{
1.848 + if (ignore_parse_level)
1.849 + {
1.850 + ignore_parse_level++;
1.851 + }
1.852 + else
1.853 + {
1.854 + if (current_parse_state == PARSE_STATE_NONE)
1.855 + {
1.856 + cur_cat = new config_category_t;
1.857 + cur_cat->name = m_Config.strings.AddString(name);
1.858 + m_Config.cats.push_back(cur_cat);
1.859 + current_parse_state = PARSE_STATE_CATEGORY;
1.860 + }
1.861 + else
1.862 + {
1.863 + ignore_parse_level = 1;
1.864 + }
1.865 + }
1.866 +
1.867 + return SMCParse_Continue;
1.868 +}
1.869 +
1.870 +SMCParseResult TopMenu::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
1.871 +{
1.872 + if (ignore_parse_level > 0
1.873 + || current_parse_state != PARSE_STATE_CATEGORY
1.874 + || cur_cat == NULL)
1.875 + {
1.876 + return SMCParse_Continue;
1.877 + }
1.878 +
1.879 + if (strcmp(key, "item") == 0)
1.880 + {
1.881 + cur_cat->commands.push_back(m_Config.strings.AddString(value));
1.882 + }
1.883 +
1.884 + return SMCParse_Continue;
1.885 +}
1.886 +
1.887 +SMCParseResult TopMenu::ReadSMC_LeavingSection()
1.888 +{
1.889 + if (ignore_parse_level)
1.890 + {
1.891 + ignore_parse_level--;
1.892 + }
1.893 + else
1.894 + {
1.895 + if (current_parse_state == PARSE_STATE_CATEGORY)
1.896 + {
1.897 + cur_cat = NULL;
1.898 + current_parse_state = PARSE_STATE_NONE;
1.899 + }
1.900 + }
1.901 +
1.902 + return SMCParse_Continue;
1.903 +}
1.904 +
1.905 +int _SortObjectNamesDescending(const void *ptr1, const void *ptr2)
1.906 +{
1.907 + obj_by_name_t *obj1 = (obj_by_name_t *)ptr1;
1.908 + obj_by_name_t *obj2 = (obj_by_name_t *)ptr2;
1.909 + return strcmp(obj1->name, obj2->name);
1.910 +}
1.911 +
1.912 +unsigned int strncopy(char *dest, const char *src, size_t count)
1.913 +{
1.914 + if (!count)
1.915 + {
1.916 + return 0;
1.917 + }
1.918 +
1.919 + char *start = dest;
1.920 + while ((*src) && (--count))
1.921 + {
1.922 + *dest++ = *src++;
1.923 + }
1.924 + *dest = '\0';
1.925 +
1.926 + return (dest - start);
1.927 +}
1.928 +
1.929 +size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
1.930 +{
1.931 + va_list ap;
1.932 + va_start(ap, fmt);
1.933 + size_t len = vsnprintf(buffer, maxlength, fmt, ap);
1.934 + va_end(ap);
1.935 +
1.936 + if (len >= maxlength)
1.937 + {
1.938 + buffer[maxlength - 1] = '\0';
1.939 + return (maxlength - 1);
1.940 + }
1.941 + else
1.942 + {
1.943 + return len;
1.944 + }
1.945 +}