diff --git a/extensions/topmenus/TopMenu.cpp b/extensions/topmenus/TopMenu.cpp
index bdb048a5..53b266ec 100644
--- a/extensions/topmenus/TopMenu.cpp
+++ b/extensions/topmenus/TopMenu.cpp
@@ -1,1105 +1,1119 @@
-/**
- * vim: set ts=4 :
- * =============================================================================
- * SourceMod Sample Extension
- * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
- * =============================================================================
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, version 3.0, as published by the
- * Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- *
- * As a special exception, AlliedModders LLC gives you permission to link the
- * code of this program (as well as its derivative works) to "Half-Life 2," the
- * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
- * by the Valve Corporation. You must obey the GNU General Public License in
- * all respects for all other code used. Additionally, AlliedModders LLC grants
- * this exception to all derivative works. AlliedModders LLC defines further
- * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
- * or .
- *
- * Version: $Id$
- */
-
-#include
-#include
-#include "TopMenu.h"
-
-struct obj_by_name_t
-{
- unsigned int obj_index;
- char name[64];
-};
-
-int _SortObjectNamesDescending(const void *ptr1, const void *ptr2);
-unsigned int strncopy(char *dest, const char *src, size_t count);
-size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
-
-TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks)
-{
- m_clients = NULL;
- m_SerialNo = 1;
- m_pTitle = callbacks;
- m_max_clients = 0;
-
- if (playerhelpers->IsServerActivated())
- {
- CreatePlayers(playerhelpers->GetMaxClients());
- }
-}
-
-TopMenu::~TopMenu()
-{
- /* Delete all categories */
- while (m_Categories.size())
- {
- RemoveFromMenu(m_Categories[0]->obj->object_id);
- }
-
- /* Remove all objects */
- for (size_t i = 0; i < m_Objects.size(); i++)
- {
- assert(m_Objects[i]->is_free == true);
- delete m_Objects[i];
- }
-
- m_pTitle->OnTopMenuObjectRemoved(this, 0);
-
- /* Delete all cached config entries */
- for (size_t i = 0; i < m_Config.cats.size(); i++)
- {
- delete m_Config.cats[i];
- }
-
- /* Sweep players */
- for (size_t i = 0; i <= (size_t)m_max_clients; i++)
- {
- TearDownClient(&m_clients[i]);
- }
- delete [] m_clients;
-}
-
-void TopMenu::OnClientConnected(int client)
-{
- if (m_clients == NULL)
- {
- return;
- }
-
- topmenu_player_t *player = &m_clients[client];
- TearDownClient(player);
-}
-
-void TopMenu::OnClientDisconnected(int client)
-{
- if (m_clients == NULL)
- {
- return;
- }
-
- topmenu_player_t *player = &m_clients[client];
- TearDownClient(player);
-}
-
-void TopMenu::OnServerActivated(int max_clients)
-{
- if (m_clients == NULL)
- {
- CreatePlayers(max_clients);
- }
-}
-
-unsigned int TopMenu::AddToMenu(const char *name,
- TopMenuObjectType type,
- ITopMenuObjectCallbacks *callbacks,
- IdentityToken_t *owner,
- const char *cmdname,
- FlagBits flags,
- unsigned int parent)
-{
- return AddToMenu2(name, type, callbacks, owner, cmdname, flags, parent, NULL);
-}
-
-unsigned int TopMenu::AddToMenu2(const char *name,
- TopMenuObjectType type,
- ITopMenuObjectCallbacks *callbacks,
- IdentityToken_t *owner,
- const char *cmdname,
- FlagBits flags,
- unsigned int parent,
- const char *info_string)
-{
- /* Sanity checks */
- if (type == TopMenuObject_Category && parent != 0)
- {
- return 0;
- }
- else if (type == TopMenuObject_Item && parent == 0)
- {
- return 0;
- }
- else if (m_ObjLookup.retrieve(name) != NULL)
- {
- return 0;
- }
- else if (type != TopMenuObject_Item && type != TopMenuObject_Category)
- {
- return 0;
- }
-
- /* If we're adding an item, make sure the parent is valid,
- * and that the parent is a category.
- */
- topmenu_object_t *parent_obj = NULL;
- topmenu_category_t *parent_cat = NULL;
- if (type == TopMenuObject_Item)
- {
- /* Check parent index. Note it will be >= 1 here. */
- if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free)
- {
- return 0;
- }
- parent_obj = m_Objects[parent - 1];
-
- /* Find an equivalent pointer in the category array. */
- for (size_t i = 0; i < m_Categories.size(); i++)
- {
- if (m_Categories[i]->obj == parent_obj)
- {
- parent_cat = m_Categories[i];
- break;
- }
- }
-
- /* If none was found, leave. */
- if (parent_cat == NULL)
- {
- return 0;
- }
- }
-
- /* Re-use an old object pointer if we can. */
- topmenu_object_t *obj = NULL;
- for (size_t i = 0; i < m_Objects.size(); i++)
- {
- if (m_Objects[i]->is_free == true)
- {
- obj = m_Objects[i];
- break;
- }
- }
-
- /* Otherwise, allocate a new one. */
- if (obj == NULL)
- {
- obj = new topmenu_object_t;
- obj->object_id = ((unsigned int)m_Objects.size()) + 1;
- m_Objects.push_back(obj);
- }
-
- /* Initialize the object's properties. */
- obj->callbacks = callbacks;
- obj->flags = flags;
- obj->owner = owner;
- obj->type = type;
- obj->is_free = false;
- obj->parent = parent_obj;
- strncopy(obj->name, name, sizeof(obj->name));
- strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname));
- strncopy(obj->info, info_string ? info_string : "", sizeof(obj->info));
-
- if (obj->type == TopMenuObject_Category)
- {
- /* Create a new category entry */
- topmenu_category_t *cat = new topmenu_category_t;
- cat->obj = obj;
- cat->reorder = false;
- cat->serial = 1;
-
- /* Add it, then update our serial change number. */
- m_Categories.push_back(cat);
- m_SerialNo++;
-
- /* Updating sorting info */
- m_bCatsNeedResort = true;
- }
- else if (obj->type == TopMenuObject_Item)
- {
- /* Update the category, mark it as needing changes */
- parent_cat->obj_list.push_back(obj);
- parent_cat->reorder = true;
- parent_cat->serial++;
-
- /* If the category just went from 0 to 1 items, mark it as
- * changed, so clients get the category drawn.
- */
- if (parent_cat->obj_list.size() == 1)
- {
- m_SerialNo++;
- }
- }
-
- m_ObjLookup.insert(name, obj);
-
- return obj->object_id;
-}
-
-const char *TopMenu::GetObjectInfoString(unsigned int object_id)
-{
- if (object_id == 0
- || object_id > m_Objects.size()
- || m_Objects[object_id - 1]->is_free)
- {
- return NULL;
- }
-
- topmenu_object_t *obj = m_Objects[object_id - 1];
-
- return obj->info;
-}
-
-void TopMenu::RemoveFromMenu(unsigned int object_id)
-{
- if (object_id == 0
- || object_id > m_Objects.size()
- || m_Objects[object_id - 1]->is_free)
- {
- return;
- }
-
- topmenu_object_t *obj = m_Objects[object_id - 1];
-
- m_ObjLookup.remove(obj->name);
-
- if (obj->type == TopMenuObject_Category)
- {
- /* Find it in the category list. */
- for (size_t i = 0; i < m_Categories.size(); i++)
- {
- if (m_Categories[i]->obj == obj)
- {
- /* Mark all children as removed + free. Note we could
- * call into RemoveMenuItem() for this, but it'd be very
- * inefficient!
- */
- topmenu_category_t *cat = m_Categories[i];
- for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++)
- {
- cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id);
- cat->obj_list[j]->is_free = true;
- }
-
- /* Remove the category from the list, then delete it. */
- m_Categories.erase(m_Categories.iterAt(i));
- delete cat;
- break;
- }
- }
-
- /* Update the root as changed. */
- m_SerialNo++;
- m_bCatsNeedResort = true;
- }
- else if (obj->type == TopMenuObject_Item)
- {
- /* Find the category this item is in. */
- topmenu_category_t *parent_cat = NULL;
- for (size_t i = 0; i < m_Categories.size(); i++)
- {
- if (m_Categories[i]->obj == obj->parent)
- {
- parent_cat = m_Categories[i];
- break;
- }
- }
-
- /* Erase it from the category's lists. */
- if (parent_cat)
- {
- for (size_t i = 0; i < parent_cat->obj_list.size(); i++)
- {
- if (parent_cat->obj_list[i] == obj)
- {
- parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i));
-
- /* If this category now has no items, mark root as changed
- * so clients won't get the category drawn anymore.
- */
- if (parent_cat->obj_list.size() == 0)
- {
- m_SerialNo++;
- }
- break;
- }
- }
-
- /* Update the category as changed. */
- parent_cat->reorder = true;
- parent_cat->serial++;
- }
- }
-
- /* The callbacks pointer is still valid, so fire away! */
- obj->callbacks->OnTopMenuObjectRemoved(this, object_id);
-
- /* Finally, mark the object as free. */
- obj->is_free = true;
-}
-
-bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position)
-{
- if (m_clients == NULL)
- {
- return false;
- }
-
- IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
- if (!pPlayer->IsInGame())
- {
- return false;
- }
-
- UpdateClientRoot(client, pPlayer);
-
- topmenu_player_t *pClient = &m_clients[client];
- if (pClient->root == NULL)
- {
- return false;
- }
-
- bool return_value = false;
-
- if (position == TopMenuPosition_LastCategory &&
- pClient->last_category < m_Categories.size())
- {
- return_value = DisplayCategory(client, pClient->last_category, hold_time, true);
- if (!return_value)
- {
- return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
- }
- }
- else if (position == TopMenuPosition_LastRoot)
- {
- pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
- }
- else if (position == TopMenuPosition_Start)
- {
- pClient->last_position = 0;
- pClient->last_category = 0;
- return_value = pClient->root->Display(client, hold_time);
- }
-
- return return_value;
-}
-
-bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position)
-{
- UpdateClientCategory(client, category);
-
- topmenu_player_t *pClient = &m_clients[client];
- if (category >= pClient->cat_count
- || pClient->cats[category].menu == NULL)
- {
- return false;
- }
-
- bool return_value = false;
-
- topmenu_player_category_t *player_cat = &(pClient->cats[category]);
-
- pClient->last_category = category;
- if (last_position)
- {
- return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position);
- }
- else
- {
- return_value = player_cat->menu->Display(client, hold_time);
- }
-
- return return_value;
-}
-
-void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
-{
- const char *item_name = menu->GetItemInfo(item, NULL);
- if (!item_name)
- {
- return;
- }
-
- topmenu_object_t *obj;
- topmenu_player_t *pClient = &m_clients[client];
- topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
- if (pObject == NULL)
- {
- return;
- }
-
- obj = *pObject;
-
- /* We now have the object... what do we do with it? */
- if (obj->type == TopMenuObject_Category)
- {
- /* If it's a category, the user wants to view it.. */
- for (size_t i = 0; i < m_Categories.size(); i++)
- {
- if (m_Categories[i]->obj == obj)
- {
- pClient->last_root_pos = item_on_page;
- if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false))
- {
- /* If we can't display the category, re-display the root menu */
- DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot);
- }
- break;
- }
- }
- }
- else
- {
- pClient->last_position = item_on_page;
-
- /* Re-check access in case this user had their credentials revoked */
- if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
- {
- DisplayMenu(client, 0, TopMenuPosition_LastCategory);
- return;
- }
-
- /* Pass the information on to the callback */
- obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id);
- }
-}
-
-void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
-{
- const char *item_name = menu->GetItemInfo(item, NULL);
- if (!item_name)
- {
- return;
- }
-
- topmenu_object_t *obj;
- topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
- if (pObject == NULL)
- {
- return;
- }
-
- obj = *pObject;
-
- style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id);
- if (style != ITEMDRAW_DEFAULT)
- {
- return;
- }
-
- if (obj->cmdname[0] == '\0')
- {
- return;
- }
-
- if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
- {
- style = ITEMDRAW_IGNORE;
- }
-}
-
-unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu,
- int client,
- IMenuPanel *panel,
- unsigned int item,
- const ItemDrawInfo &dr)
-{
- const char *item_name = menu->GetItemInfo(item, NULL);
- if (!item_name)
- {
- return 0;
- }
-
- topmenu_object_t *obj;
- topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
- if (pObject == NULL)
- {
- return 0;
- }
-
- obj = *pObject;
-
- /* Ask the object to render the text for this client */
- char renderbuf[64];
- obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf));
-
- /* Build the new draw info */
- ItemDrawInfo new_dr = dr;
- new_dr.display = renderbuf;
-
- /* Man I love the menu API. Ask the panel to draw the item and give the position
- * back to Core's renderer. This way we don't have to worry about keeping the
- * render buffer static!
- */
- return panel->DrawItem(new_dr);
-}
-
-void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
-{
- if (reason == MenuCancel_ExitBack)
- {
- /* If the client chose exit back, they were on a category menu, and we can
- * now display the root menu from the last known position.
- */
- DisplayMenu(client, 0, TopMenuPosition_LastRoot);
- }
-}
-
-void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer)
-{
- topmenu_player_t *pClient = &m_clients[client];
- IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client);
-
- /* Determine if an update is necessary */
- bool is_update_needed = false;
- if (pClient->menu_serial != m_SerialNo)
- {
- is_update_needed = true;
- }
- else if (pPlayer->GetUserId() != pClient->user_id)
- {
- is_update_needed = true;
- }
-
- /* If no update is needed at the root level, just leave now */
- if (!is_update_needed)
- {
- return;
- }
-
- /* First we need to flush the cache... */
- TearDownClient(pClient);
-
- /* Now, rebuild the category list, but don't create menus */
- if (m_Categories.size() == 0)
- {
- pClient->cat_count = 0;
- pClient->cats = NULL;
- }
- else
- {
- pClient->cat_count = (unsigned int)m_Categories.size();
- pClient->cats = new topmenu_player_category_t[pClient->cat_count];
- memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count);
- }
-
- /* Re-sort the root categories if needed */
- SortCategoriesIfNeeded();
-
- /* Build the root menu */
- IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
-
- /* Add the sorted items */
- for (size_t i = 0; i < m_SortedCats.size(); i++)
- {
- if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0)
- {
- continue;
- }
- root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo(""));
- }
-
- /* Now we need to handle un-sorted items. This is slightly trickier, as we need to
- * pre-render each category name, and cache those names. Phew!
- */
- if (m_UnsortedCats.size())
- {
- obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()];
- for (size_t i = 0; i < m_UnsortedCats.size(); i++)
- {
- obj_by_name_t *temp_obj = &item_list[i];
- topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj;
- obj->callbacks->OnTopMenuDisplayOption(this,
- client,
- obj->object_id,
- temp_obj->name,
- sizeof(temp_obj->name));
- temp_obj->obj_index = m_UnsortedCats[i];
- }
-
- /* Sort our temp list */
- qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
-
- /* Add the new sorted categories */
- for (size_t i = 0; i < m_UnsortedCats.size(); i++)
- {
- if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0)
- {
- continue;
- }
- root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo(""));
- }
-
- delete [] item_list;
- }
-
- /* Set the menu's title */
- char renderbuf[128];
- m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf));
- root_menu->SetDefaultTitle(renderbuf);
-
- /* The client is now fully updated */
- pClient->root = root_menu;
- pClient->user_id = pPlayer->GetUserId();
- pClient->menu_serial = m_SerialNo;
- pClient->last_position = 0;
- pClient->last_category = 0;
- pClient->last_root_pos = 0;
-}
-
-void TopMenu::UpdateClientCategory(int client, unsigned int category)
-{
- /* Update the client's root menu just in case it needs it. This
- * will validate that we have both a valid client and a valid
- * category structure for that client.
- */
- UpdateClientRoot(client);
-
- /* Now it's guaranteed that our category tables will be usable */
- topmenu_player_t *pClient = &m_clients[client];
- topmenu_category_t *cat = m_Categories[category];
- topmenu_player_category_t *player_cat = &(pClient->cats[category]);
-
- /* Does the category actually need updating? */
- if (player_cat->serial == cat->serial)
- {
- return;
- }
-
- /* Destroy any existing menu */
- if (player_cat->menu)
- {
- player_cat->menu->Destroy();
- player_cat->menu = NULL;
- }
-
- if (pClient->last_category == category)
- {
- pClient->last_position = 0;
- }
-
- IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
-
- /* Categories get an "exit back" button */
- cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK);
-
- /* Re-sort the category if needed */
- SortCategoryIfNeeded(category);
-
- /* Build the menu with the sorted items first */
- for (size_t i = 0; i < cat->sorted.size(); i++)
- {
- cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo(""));
- }
-
- /* Now handle unsorted items */
- if (cat->unsorted.size())
- {
- /* Build a list of the item names */
- obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()];
- for (size_t i = 0; i < cat->unsorted.size(); i++)
- {
- obj_by_name_t *item = &item_list[i];
- topmenu_object_t *obj = cat->unsorted[i];
- obj->callbacks->OnTopMenuDisplayOption(this,
- client,
- obj->object_id,
- item->name,
- sizeof(item->name));
- item->obj_index = (unsigned int)i;
- }
-
- /* Sort the names */
- qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
-
- /* Add to the menu */
- for (size_t i = 0; i < cat->unsorted.size(); i++)
- {
- cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo(""));
- }
-
- delete [] item_list;
- }
-
- /* Set the menu's title */
- char renderbuf[128];
- cat->obj->callbacks->OnTopMenuDisplayTitle(this,
- client,
- cat->obj->object_id,
- renderbuf,
- sizeof(renderbuf));
- cat_menu->SetDefaultTitle(renderbuf);
-
- /* We're done! */
- player_cat->menu = cat_menu;
- player_cat->serial = cat->serial;
-}
-
-void TopMenu::SortCategoryIfNeeded(unsigned int category)
-{
- topmenu_category_t *cat = m_Categories[category];
- if (!cat->reorder)
- {
- return;
- }
-
- cat->sorted.clear();
- cat->unsorted.clear();
-
- if (cat->obj_list.size() == 0)
- {
- cat->reorder = false;
- return;
- }
-
- CVector to_sort;
- for (size_t i = 0; i < cat->obj_list.size(); i++)
- {
- to_sort.push_back(i);
- }
-
- /* Find a matching category in the configs */
- config_category_t *config_cat = NULL;
- for (size_t i = 0; i < m_Config.cats.size(); i++)
- {
- if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0)
- {
- config_cat = m_Config.cats[i];
- break;
- }
- }
-
- /* If there is a matching category, build a pre-sorted item list */
- if (config_cat != NULL)
- {
- /* Find matching objects in this category */
- for (size_t i = 0; i < config_cat->commands.size(); i++)
- {
- const char *config_name = m_Config.strings.GetString(config_cat->commands[i]);
- for (size_t j = 0; j < to_sort.size(); j++)
- {
- if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0)
- {
- /* Place in the final list, then remove from the temporary list */
- cat->sorted.push_back(cat->obj_list[to_sort[j]]);
- to_sort.erase(to_sort.iterAt(j));
- break;
- }
- }
- }
- }
-
- /* Push any remaining items onto the unsorted list */
- for (size_t i = 0; i < to_sort.size(); i++)
- {
- cat->unsorted.push_back(cat->obj_list[to_sort[i]]);
- }
-
- cat->reorder = false;
-}
-
-void TopMenu::SortCategoriesIfNeeded()
-{
- if (!m_bCatsNeedResort)
- {
- return;
- }
-
- /* Clear sort results */
- m_SortedCats.clear();
- m_UnsortedCats.clear();
-
- if (m_Categories.size() == 0)
- {
- m_bCatsNeedResort = false;
- return;
- }
-
- CVector to_sort;
- for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++)
- {
- to_sort.push_back(i);
- }
-
- /* If we have any predefined categories, add them in as they appear. */
- for (size_t i= 0; i < m_Config.cats.size(); i++)
- {
- /* Find this category and map it in if we can */
- for (size_t j = 0; j < to_sort.size(); j++)
- {
- if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name),
- m_Categories[to_sort[j]]->obj->name) == 0)
- {
- /* Add to the real list and remove from the temporary */
- m_SortedCats.push_back(to_sort[j]);
- to_sort.erase(to_sort.iterAt(j));
- break;
- }
- }
- }
-
- /* Push any remaining items onto the unsorted list */
- for (size_t i = 0; i < to_sort.size(); i++)
- {
- m_UnsortedCats.push_back(to_sort[i]);
- }
-
- m_bCatsNeedResort = false;
-}
-
-void TopMenu::CreatePlayers(int max_clients)
-{
- m_max_clients = max_clients;
- m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1));
- memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1));
-}
-
-void TopMenu::TearDownClient(topmenu_player_t *player)
-{
- if (player->cats != NULL)
- {
- for (unsigned int i = 0; i < player->cat_count; i++)
- {
- topmenu_player_category_t *player_cat = &(player->cats[i]);
- if (player_cat->menu != NULL)
- {
- player_cat->menu->Destroy();
- }
- }
- delete [] player->cats;
- }
-
- if (player->root != NULL)
- {
- player->root->Destroy();
- }
-
- memset(player, 0, sizeof(topmenu_player_t));
-}
-
-bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength)
-{
- SMCError err;
- SMCStates states;
-
- if ((err = textparsers->ParseFile_SMC(file, this, &states))
- != SMCError_Okay)
- {
- const char *err_string = textparsers->GetSMCErrorString(err);
- if (!err_string)
- {
- err_string = "Unknown";
- }
-
- UTIL_Format(error, maxlength, "%s", err_string);
-
- return false;
- }
-
- return true;
-}
-
-bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner)
-{
- /* First sweep the categories owned by us */
- CVector obj_list;
- for (size_t i = 0; i < m_Categories.size(); i++)
- {
- if (m_Categories[i]->obj->owner == owner)
- {
- obj_list.push_back(m_Categories[i]->obj->object_id);
- }
- }
-
- for (size_t i = 0; i < obj_list.size(); i++)
- {
- RemoveFromMenu(obj_list[i]);
- }
-
- /* Now we can look for actual items */
- for (size_t i = 0; i < m_Objects.size(); i++)
- {
- if (m_Objects[i]->is_free)
- {
- continue;
- }
- if (m_Objects[i]->owner == owner)
- {
- assert(m_Objects[i]->type != TopMenuObject_Category);
- RemoveFromMenu(m_Objects[i]->object_id);
- }
- }
-
- return true;
-}
-
-#define PARSE_STATE_NONE 0
-#define PARSE_STATE_MAIN 1
-#define PARSE_STATE_CATEGORY 2
-unsigned int ignore_parse_level = 0;
-unsigned int current_parse_state = 0;
-config_category_t *cur_cat = NULL;
-
-void TopMenu::ReadSMC_ParseStart()
-{
- current_parse_state = PARSE_STATE_NONE;
- ignore_parse_level = 0;
- cur_cat = NULL;
-
- /* Reset the old config */
- m_Config.strings.Reset();
- for (size_t i = 0; i < m_Config.cats.size(); i++)
- {
- delete m_Config.cats[i];
- }
- m_Config.cats.clear();
-}
-
-SMCResult TopMenu::ReadSMC_NewSection(const SMCStates *states, const char *name)
-{
- if (ignore_parse_level)
- {
- ignore_parse_level++;
- }
- else
- {
- if (current_parse_state == PARSE_STATE_NONE)
- {
- if (strcmp(name, "Menu") == 0)
- {
- current_parse_state = PARSE_STATE_MAIN;
- }
- else
- {
- ignore_parse_level = 1;
- }
- }
- else if (current_parse_state == PARSE_STATE_MAIN)
- {
- cur_cat = new config_category_t;
- cur_cat->name = m_Config.strings.AddString(name);
- m_Config.cats.push_back(cur_cat);
- current_parse_state = PARSE_STATE_CATEGORY;
- }
- else
- {
- ignore_parse_level = 1;
- }
- }
-
- return SMCResult_Continue;
-}
-
-SMCResult TopMenu::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
-{
- if (ignore_parse_level > 0
- || current_parse_state != PARSE_STATE_CATEGORY
- || cur_cat == NULL)
- {
- return SMCResult_Continue;
- }
-
- if (strcmp(key, "item") == 0)
- {
- cur_cat->commands.push_back(m_Config.strings.AddString(value));
- }
-
- return SMCResult_Continue;
-}
-
-SMCResult TopMenu::ReadSMC_LeavingSection(const SMCStates *states)
-{
- if (ignore_parse_level)
- {
- ignore_parse_level--;
- }
- else
- {
- if (current_parse_state == PARSE_STATE_CATEGORY)
- {
- cur_cat = NULL;
- current_parse_state = PARSE_STATE_MAIN;
- }
- else if (current_parse_state == PARSE_STATE_MAIN)
- {
- current_parse_state = PARSE_STATE_NONE;
- }
- }
-
- return SMCResult_Continue;
-}
-
-unsigned int TopMenu::FindCategory(const char *name)
-{
- topmenu_object_t **p_obj = m_ObjLookup.retrieve(name);
- if (!p_obj)
- {
- return 0;
- }
-
- topmenu_object_t *obj = *p_obj;
- if (obj->type != TopMenuObject_Category)
- {
- return 0;
- }
-
- return obj->object_id;
-}
-
-int _SortObjectNamesDescending(const void *ptr1, const void *ptr2)
-{
- obj_by_name_t *obj1 = (obj_by_name_t *)ptr1;
- obj_by_name_t *obj2 = (obj_by_name_t *)ptr2;
- return strcmp(obj1->name, obj2->name);
-}
-
-unsigned int strncopy(char *dest, const char *src, size_t count)
-{
- if (!count)
- {
- return 0;
- }
-
- char *start = dest;
- while ((*src) && (--count))
- {
- *dest++ = *src++;
- }
- *dest = '\0';
-
- return (dest - start);
-}
-
-size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- size_t len = vsnprintf(buffer, maxlength, fmt, ap);
- va_end(ap);
-
- if (len >= maxlength)
- {
- buffer[maxlength - 1] = '\0';
- return (maxlength - 1);
- }
- else
- {
- return len;
- }
-}
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod Sample Extension
+ * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#include
+#include
+#include "TopMenu.h"
+
+struct obj_by_name_t
+{
+ unsigned int obj_index;
+ char name[64];
+};
+
+int _SortObjectNamesDescending(const void *ptr1, const void *ptr2);
+unsigned int strncopy(char *dest, const char *src, size_t count);
+size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...);
+
+TopMenu::TopMenu(ITopMenuObjectCallbacks *callbacks)
+{
+ m_clients = NULL;
+ m_SerialNo = 1;
+ m_pTitle = callbacks;
+ m_max_clients = 0;
+
+ if (playerhelpers->IsServerActivated())
+ {
+ CreatePlayers(playerhelpers->GetMaxClients());
+ }
+}
+
+TopMenu::~TopMenu()
+{
+ /* Delete all categories */
+ while (m_Categories.size())
+ {
+ RemoveFromMenu(m_Categories[0]->obj->object_id);
+ }
+
+ /* Remove all objects */
+ for (size_t i = 0; i < m_Objects.size(); i++)
+ {
+ assert(m_Objects[i]->is_free == true);
+ delete m_Objects[i];
+ }
+
+ m_pTitle->OnTopMenuObjectRemoved(this, 0);
+
+ /* Delete all cached config entries */
+ for (size_t i = 0; i < m_Config.cats.size(); i++)
+ {
+ delete m_Config.cats[i];
+ }
+
+ /* Sweep players */
+ for (size_t i = 0; i <= (size_t)m_max_clients; i++)
+ {
+ TearDownClient(&m_clients[i]);
+ }
+ delete [] m_clients;
+}
+
+void TopMenu::OnClientConnected(int client)
+{
+ if (m_clients == NULL)
+ {
+ return;
+ }
+
+ topmenu_player_t *player = &m_clients[client];
+ TearDownClient(player);
+}
+
+void TopMenu::OnClientDisconnected(int client)
+{
+ if (m_clients == NULL)
+ {
+ return;
+ }
+
+ topmenu_player_t *player = &m_clients[client];
+ TearDownClient(player);
+}
+
+void TopMenu::OnServerActivated(int max_clients)
+{
+ if (m_clients == NULL)
+ {
+ CreatePlayers(max_clients);
+ }
+}
+
+unsigned int TopMenu::AddToMenu(const char *name,
+ TopMenuObjectType type,
+ ITopMenuObjectCallbacks *callbacks,
+ IdentityToken_t *owner,
+ const char *cmdname,
+ FlagBits flags,
+ unsigned int parent)
+{
+ return AddToMenu2(name, type, callbacks, owner, cmdname, flags, parent, NULL);
+}
+
+unsigned int TopMenu::AddToMenu2(const char *name,
+ TopMenuObjectType type,
+ ITopMenuObjectCallbacks *callbacks,
+ IdentityToken_t *owner,
+ const char *cmdname,
+ FlagBits flags,
+ unsigned int parent,
+ const char *info_string)
+{
+ /* Sanity checks */
+ if (type == TopMenuObject_Category && parent != 0)
+ {
+ return 0;
+ }
+ else if (type == TopMenuObject_Item && parent == 0)
+ {
+ return 0;
+ }
+ else if (m_ObjLookup.retrieve(name) != NULL)
+ {
+ return 0;
+ }
+ else if (type != TopMenuObject_Item && type != TopMenuObject_Category)
+ {
+ return 0;
+ }
+
+ /* If we're adding an item, make sure the parent is valid,
+ * and that the parent is a category.
+ */
+ topmenu_object_t *parent_obj = NULL;
+ topmenu_category_t *parent_cat = NULL;
+ if (type == TopMenuObject_Item)
+ {
+ /* Check parent index. Note it will be >= 1 here. */
+ if (parent > m_Objects.size() || m_Objects[parent - 1]->is_free)
+ {
+ return 0;
+ }
+ parent_obj = m_Objects[parent - 1];
+
+ /* Find an equivalent pointer in the category array. */
+ for (size_t i = 0; i < m_Categories.size(); i++)
+ {
+ if (m_Categories[i]->obj == parent_obj)
+ {
+ parent_cat = m_Categories[i];
+ break;
+ }
+ }
+
+ /* If none was found, leave. */
+ if (parent_cat == NULL)
+ {
+ return 0;
+ }
+ }
+
+ /* Re-use an old object pointer if we can. */
+ topmenu_object_t *obj = NULL;
+ for (size_t i = 0; i < m_Objects.size(); i++)
+ {
+ if (m_Objects[i]->is_free == true)
+ {
+ obj = m_Objects[i];
+ break;
+ }
+ }
+
+ /* Otherwise, allocate a new one. */
+ if (obj == NULL)
+ {
+ obj = new topmenu_object_t;
+ obj->object_id = ((unsigned int)m_Objects.size()) + 1;
+ m_Objects.push_back(obj);
+ }
+
+ /* Initialize the object's properties. */
+ obj->callbacks = callbacks;
+ obj->flags = flags;
+ obj->owner = owner;
+ obj->type = type;
+ obj->is_free = false;
+ obj->parent = parent_obj;
+ strncopy(obj->name, name, sizeof(obj->name));
+ strncopy(obj->cmdname, cmdname ? cmdname : "", sizeof(obj->cmdname));
+ strncopy(obj->info, info_string ? info_string : "", sizeof(obj->info));
+
+ if (obj->type == TopMenuObject_Category)
+ {
+ /* Create a new category entry */
+ topmenu_category_t *cat = new topmenu_category_t;
+ cat->obj = obj;
+ cat->reorder = false;
+ cat->serial = 1;
+
+ /* Add it, then update our serial change number. */
+ m_Categories.push_back(cat);
+ m_SerialNo++;
+
+ /* Updating sorting info */
+ m_bCatsNeedResort = true;
+ }
+ else if (obj->type == TopMenuObject_Item)
+ {
+ /* Update the category, mark it as needing changes */
+ parent_cat->obj_list.push_back(obj);
+ parent_cat->reorder = true;
+ parent_cat->serial++;
+
+ /* If the category just went from 0 to 1 items, mark it as
+ * changed, so clients get the category drawn.
+ */
+ if (parent_cat->obj_list.size() == 1)
+ {
+ m_SerialNo++;
+ }
+ }
+
+ m_ObjLookup.insert(name, obj);
+
+ return obj->object_id;
+}
+
+const char *TopMenu::GetObjectInfoString(unsigned int object_id)
+{
+ if (object_id == 0
+ || object_id > m_Objects.size()
+ || m_Objects[object_id - 1]->is_free)
+ {
+ return NULL;
+ }
+
+ topmenu_object_t *obj = m_Objects[object_id - 1];
+
+ return obj->info;
+}
+
+const char *TopMenu::GetObjectName(unsigned int object_id)
+{
+ if (object_id == 0
+ || object_id > m_Objects.size()
+ || m_Objects[object_id - 1]->is_free)
+ {
+ return NULL;
+ }
+
+ topmenu_object_t *obj = m_Objects[object_id - 1];
+
+ return obj->name;
+}
+
+void TopMenu::RemoveFromMenu(unsigned int object_id)
+{
+ if (object_id == 0
+ || object_id > m_Objects.size()
+ || m_Objects[object_id - 1]->is_free)
+ {
+ return;
+ }
+
+ topmenu_object_t *obj = m_Objects[object_id - 1];
+
+ m_ObjLookup.remove(obj->name);
+
+ if (obj->type == TopMenuObject_Category)
+ {
+ /* Find it in the category list. */
+ for (size_t i = 0; i < m_Categories.size(); i++)
+ {
+ if (m_Categories[i]->obj == obj)
+ {
+ /* Mark all children as removed + free. Note we could
+ * call into RemoveMenuItem() for this, but it'd be very
+ * inefficient!
+ */
+ topmenu_category_t *cat = m_Categories[i];
+ for (size_t j = 0; j < m_Categories[i]->obj_list.size(); j++)
+ {
+ cat->obj_list[j]->callbacks->OnTopMenuObjectRemoved(this, cat->obj_list[j]->object_id);
+ cat->obj_list[j]->is_free = true;
+ }
+
+ /* Remove the category from the list, then delete it. */
+ m_Categories.erase(m_Categories.iterAt(i));
+ delete cat;
+ break;
+ }
+ }
+
+ /* Update the root as changed. */
+ m_SerialNo++;
+ m_bCatsNeedResort = true;
+ }
+ else if (obj->type == TopMenuObject_Item)
+ {
+ /* Find the category this item is in. */
+ topmenu_category_t *parent_cat = NULL;
+ for (size_t i = 0; i < m_Categories.size(); i++)
+ {
+ if (m_Categories[i]->obj == obj->parent)
+ {
+ parent_cat = m_Categories[i];
+ break;
+ }
+ }
+
+ /* Erase it from the category's lists. */
+ if (parent_cat)
+ {
+ for (size_t i = 0; i < parent_cat->obj_list.size(); i++)
+ {
+ if (parent_cat->obj_list[i] == obj)
+ {
+ parent_cat->obj_list.erase(parent_cat->obj_list.iterAt(i));
+
+ /* If this category now has no items, mark root as changed
+ * so clients won't get the category drawn anymore.
+ */
+ if (parent_cat->obj_list.size() == 0)
+ {
+ m_SerialNo++;
+ }
+ break;
+ }
+ }
+
+ /* Update the category as changed. */
+ parent_cat->reorder = true;
+ parent_cat->serial++;
+ }
+ }
+
+ /* The callbacks pointer is still valid, so fire away! */
+ obj->callbacks->OnTopMenuObjectRemoved(this, object_id);
+
+ /* Finally, mark the object as free. */
+ obj->is_free = true;
+}
+
+bool TopMenu::DisplayMenu(int client, unsigned int hold_time, TopMenuPosition position)
+{
+ if (m_clients == NULL)
+ {
+ return false;
+ }
+
+ IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
+ if (!pPlayer->IsInGame())
+ {
+ return false;
+ }
+
+ UpdateClientRoot(client, pPlayer);
+
+ topmenu_player_t *pClient = &m_clients[client];
+ if (pClient->root == NULL)
+ {
+ return false;
+ }
+
+ bool return_value = false;
+
+ if (position == TopMenuPosition_LastCategory &&
+ pClient->last_category < m_Categories.size())
+ {
+ return_value = DisplayCategory(client, pClient->last_category, hold_time, true);
+ if (!return_value)
+ {
+ return_value = pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
+ }
+ }
+ else if (position == TopMenuPosition_LastRoot)
+ {
+ pClient->root->DisplayAtItem(client, hold_time, pClient->last_root_pos);
+ }
+ else if (position == TopMenuPosition_Start)
+ {
+ pClient->last_position = 0;
+ pClient->last_category = 0;
+ return_value = pClient->root->Display(client, hold_time);
+ }
+
+ return return_value;
+}
+
+bool TopMenu::DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position)
+{
+ UpdateClientCategory(client, category);
+
+ topmenu_player_t *pClient = &m_clients[client];
+ if (category >= pClient->cat_count
+ || pClient->cats[category].menu == NULL)
+ {
+ return false;
+ }
+
+ bool return_value = false;
+
+ topmenu_player_category_t *player_cat = &(pClient->cats[category]);
+
+ pClient->last_category = category;
+ if (last_position)
+ {
+ return_value = player_cat->menu->DisplayAtItem(client, hold_time, pClient->last_position);
+ }
+ else
+ {
+ return_value = player_cat->menu->Display(client, hold_time);
+ }
+
+ return return_value;
+}
+
+void TopMenu::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
+{
+ const char *item_name = menu->GetItemInfo(item, NULL);
+ if (!item_name)
+ {
+ return;
+ }
+
+ topmenu_object_t *obj;
+ topmenu_player_t *pClient = &m_clients[client];
+ topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
+ if (pObject == NULL)
+ {
+ return;
+ }
+
+ obj = *pObject;
+
+ /* We now have the object... what do we do with it? */
+ if (obj->type == TopMenuObject_Category)
+ {
+ /* If it's a category, the user wants to view it.. */
+ for (size_t i = 0; i < m_Categories.size(); i++)
+ {
+ if (m_Categories[i]->obj == obj)
+ {
+ pClient->last_root_pos = item_on_page;
+ if (!DisplayCategory(client, (unsigned int)i, MENU_TIME_FOREVER, false))
+ {
+ /* If we can't display the category, re-display the root menu */
+ DisplayMenu(client, MENU_TIME_FOREVER, TopMenuPosition_LastRoot);
+ }
+ break;
+ }
+ }
+ }
+ else
+ {
+ pClient->last_position = item_on_page;
+
+ /* Re-check access in case this user had their credentials revoked */
+ if (obj->cmdname[0] != '\0' && !adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
+ {
+ DisplayMenu(client, 0, TopMenuPosition_LastCategory);
+ return;
+ }
+
+ /* Pass the information on to the callback */
+ obj->callbacks->OnTopMenuSelectOption(this, client, obj->object_id);
+ }
+}
+
+void TopMenu::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
+{
+ const char *item_name = menu->GetItemInfo(item, NULL);
+ if (!item_name)
+ {
+ return;
+ }
+
+ topmenu_object_t *obj;
+ topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
+ if (pObject == NULL)
+ {
+ return;
+ }
+
+ obj = *pObject;
+
+ style = obj->callbacks->OnTopMenuDrawOption(this, client, obj->object_id);
+ if (style != ITEMDRAW_DEFAULT)
+ {
+ return;
+ }
+
+ if (obj->cmdname[0] == '\0')
+ {
+ return;
+ }
+
+ if (!adminsys->CheckAccess(client, obj->cmdname, obj->flags, false))
+ {
+ style = ITEMDRAW_IGNORE;
+ }
+}
+
+unsigned int TopMenu::OnMenuDisplayItem(IBaseMenu *menu,
+ int client,
+ IMenuPanel *panel,
+ unsigned int item,
+ const ItemDrawInfo &dr)
+{
+ const char *item_name = menu->GetItemInfo(item, NULL);
+ if (!item_name)
+ {
+ return 0;
+ }
+
+ topmenu_object_t *obj;
+ topmenu_object_t **pObject = m_ObjLookup.retrieve(item_name);
+ if (pObject == NULL)
+ {
+ return 0;
+ }
+
+ obj = *pObject;
+
+ /* Ask the object to render the text for this client */
+ char renderbuf[64];
+ obj->callbacks->OnTopMenuDisplayOption(this, client, obj->object_id, renderbuf, sizeof(renderbuf));
+
+ /* Build the new draw info */
+ ItemDrawInfo new_dr = dr;
+ new_dr.display = renderbuf;
+
+ /* Man I love the menu API. Ask the panel to draw the item and give the position
+ * back to Core's renderer. This way we don't have to worry about keeping the
+ * render buffer static!
+ */
+ return panel->DrawItem(new_dr);
+}
+
+void TopMenu::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
+{
+ if (reason == MenuCancel_ExitBack)
+ {
+ /* If the client chose exit back, they were on a category menu, and we can
+ * now display the root menu from the last known position.
+ */
+ DisplayMenu(client, 0, TopMenuPosition_LastRoot);
+ }
+}
+
+void TopMenu::UpdateClientRoot(int client, IGamePlayer *pGamePlayer)
+{
+ topmenu_player_t *pClient = &m_clients[client];
+ IGamePlayer *pPlayer = pGamePlayer ? pGamePlayer : playerhelpers->GetGamePlayer(client);
+
+ /* Determine if an update is necessary */
+ bool is_update_needed = false;
+ if (pClient->menu_serial != m_SerialNo)
+ {
+ is_update_needed = true;
+ }
+ else if (pPlayer->GetUserId() != pClient->user_id)
+ {
+ is_update_needed = true;
+ }
+
+ /* If no update is needed at the root level, just leave now */
+ if (!is_update_needed)
+ {
+ return;
+ }
+
+ /* First we need to flush the cache... */
+ TearDownClient(pClient);
+
+ /* Now, rebuild the category list, but don't create menus */
+ if (m_Categories.size() == 0)
+ {
+ pClient->cat_count = 0;
+ pClient->cats = NULL;
+ }
+ else
+ {
+ pClient->cat_count = (unsigned int)m_Categories.size();
+ pClient->cats = new topmenu_player_category_t[pClient->cat_count];
+ memset(pClient->cats, 0, sizeof(topmenu_player_category_t) * pClient->cat_count);
+ }
+
+ /* Re-sort the root categories if needed */
+ SortCategoriesIfNeeded();
+
+ /* Build the root menu */
+ IBaseMenu *root_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
+
+ /* Add the sorted items */
+ for (size_t i = 0; i < m_SortedCats.size(); i++)
+ {
+ if (m_Categories[m_SortedCats[i]]->obj_list.size() == 0)
+ {
+ continue;
+ }
+ root_menu->AppendItem(m_Categories[m_SortedCats[i]]->obj->name, ItemDrawInfo(""));
+ }
+
+ /* Now we need to handle un-sorted items. This is slightly trickier, as we need to
+ * pre-render each category name, and cache those names. Phew!
+ */
+ if (m_UnsortedCats.size())
+ {
+ obj_by_name_t *item_list = new obj_by_name_t[m_UnsortedCats.size()];
+ for (size_t i = 0; i < m_UnsortedCats.size(); i++)
+ {
+ obj_by_name_t *temp_obj = &item_list[i];
+ topmenu_object_t *obj = m_Categories[m_UnsortedCats[i]]->obj;
+ obj->callbacks->OnTopMenuDisplayOption(this,
+ client,
+ obj->object_id,
+ temp_obj->name,
+ sizeof(temp_obj->name));
+ temp_obj->obj_index = m_UnsortedCats[i];
+ }
+
+ /* Sort our temp list */
+ qsort(item_list, m_UnsortedCats.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
+
+ /* Add the new sorted categories */
+ for (size_t i = 0; i < m_UnsortedCats.size(); i++)
+ {
+ if (m_Categories[item_list[i].obj_index]->obj_list.size() == 0)
+ {
+ continue;
+ }
+ root_menu->AppendItem(m_Categories[item_list[i].obj_index]->obj->name, ItemDrawInfo(""));
+ }
+
+ delete [] item_list;
+ }
+
+ /* Set the menu's title */
+ char renderbuf[128];
+ m_pTitle->OnTopMenuDisplayTitle(this, client, 0, renderbuf, sizeof(renderbuf));
+ root_menu->SetDefaultTitle(renderbuf);
+
+ /* The client is now fully updated */
+ pClient->root = root_menu;
+ pClient->user_id = pPlayer->GetUserId();
+ pClient->menu_serial = m_SerialNo;
+ pClient->last_position = 0;
+ pClient->last_category = 0;
+ pClient->last_root_pos = 0;
+}
+
+void TopMenu::UpdateClientCategory(int client, unsigned int category)
+{
+ /* Update the client's root menu just in case it needs it. This
+ * will validate that we have both a valid client and a valid
+ * category structure for that client.
+ */
+ UpdateClientRoot(client);
+
+ /* Now it's guaranteed that our category tables will be usable */
+ topmenu_player_t *pClient = &m_clients[client];
+ topmenu_category_t *cat = m_Categories[category];
+ topmenu_player_category_t *player_cat = &(pClient->cats[category]);
+
+ /* Does the category actually need updating? */
+ if (player_cat->serial == cat->serial)
+ {
+ return;
+ }
+
+ /* Destroy any existing menu */
+ if (player_cat->menu)
+ {
+ player_cat->menu->Destroy();
+ player_cat->menu = NULL;
+ }
+
+ if (pClient->last_category == category)
+ {
+ pClient->last_position = 0;
+ }
+
+ IBaseMenu *cat_menu = menus->GetDefaultStyle()->CreateMenu(this, myself->GetIdentity());
+
+ /* Categories get an "exit back" button */
+ cat_menu->SetMenuOptionFlags(cat_menu->GetMenuOptionFlags() | MENUFLAG_BUTTON_EXITBACK);
+
+ /* Re-sort the category if needed */
+ SortCategoryIfNeeded(category);
+
+ /* Build the menu with the sorted items first */
+ for (size_t i = 0; i < cat->sorted.size(); i++)
+ {
+ cat_menu->AppendItem(cat->sorted[i]->name, ItemDrawInfo(""));
+ }
+
+ /* Now handle unsorted items */
+ if (cat->unsorted.size())
+ {
+ /* Build a list of the item names */
+ obj_by_name_t *item_list = new obj_by_name_t[cat->unsorted.size()];
+ for (size_t i = 0; i < cat->unsorted.size(); i++)
+ {
+ obj_by_name_t *item = &item_list[i];
+ topmenu_object_t *obj = cat->unsorted[i];
+ obj->callbacks->OnTopMenuDisplayOption(this,
+ client,
+ obj->object_id,
+ item->name,
+ sizeof(item->name));
+ item->obj_index = (unsigned int)i;
+ }
+
+ /* Sort the names */
+ qsort(item_list, cat->unsorted.size(), sizeof(obj_by_name_t), _SortObjectNamesDescending);
+
+ /* Add to the menu */
+ for (size_t i = 0; i < cat->unsorted.size(); i++)
+ {
+ cat_menu->AppendItem(cat->unsorted[item_list[i].obj_index]->name, ItemDrawInfo(""));
+ }
+
+ delete [] item_list;
+ }
+
+ /* Set the menu's title */
+ char renderbuf[128];
+ cat->obj->callbacks->OnTopMenuDisplayTitle(this,
+ client,
+ cat->obj->object_id,
+ renderbuf,
+ sizeof(renderbuf));
+ cat_menu->SetDefaultTitle(renderbuf);
+
+ /* We're done! */
+ player_cat->menu = cat_menu;
+ player_cat->serial = cat->serial;
+}
+
+void TopMenu::SortCategoryIfNeeded(unsigned int category)
+{
+ topmenu_category_t *cat = m_Categories[category];
+ if (!cat->reorder)
+ {
+ return;
+ }
+
+ cat->sorted.clear();
+ cat->unsorted.clear();
+
+ if (cat->obj_list.size() == 0)
+ {
+ cat->reorder = false;
+ return;
+ }
+
+ CVector to_sort;
+ for (size_t i = 0; i < cat->obj_list.size(); i++)
+ {
+ to_sort.push_back(i);
+ }
+
+ /* Find a matching category in the configs */
+ config_category_t *config_cat = NULL;
+ for (size_t i = 0; i < m_Config.cats.size(); i++)
+ {
+ if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name), cat->obj->name) == 0)
+ {
+ config_cat = m_Config.cats[i];
+ break;
+ }
+ }
+
+ /* If there is a matching category, build a pre-sorted item list */
+ if (config_cat != NULL)
+ {
+ /* Find matching objects in this category */
+ for (size_t i = 0; i < config_cat->commands.size(); i++)
+ {
+ const char *config_name = m_Config.strings.GetString(config_cat->commands[i]);
+ for (size_t j = 0; j < to_sort.size(); j++)
+ {
+ if (strcmp(config_name, cat->obj_list[to_sort[j]]->name) == 0)
+ {
+ /* Place in the final list, then remove from the temporary list */
+ cat->sorted.push_back(cat->obj_list[to_sort[j]]);
+ to_sort.erase(to_sort.iterAt(j));
+ break;
+ }
+ }
+ }
+ }
+
+ /* Push any remaining items onto the unsorted list */
+ for (size_t i = 0; i < to_sort.size(); i++)
+ {
+ cat->unsorted.push_back(cat->obj_list[to_sort[i]]);
+ }
+
+ cat->reorder = false;
+}
+
+void TopMenu::SortCategoriesIfNeeded()
+{
+ if (!m_bCatsNeedResort)
+ {
+ return;
+ }
+
+ /* Clear sort results */
+ m_SortedCats.clear();
+ m_UnsortedCats.clear();
+
+ if (m_Categories.size() == 0)
+ {
+ m_bCatsNeedResort = false;
+ return;
+ }
+
+ CVector to_sort;
+ for (unsigned int i = 0; i < (unsigned int)m_Categories.size(); i++)
+ {
+ to_sort.push_back(i);
+ }
+
+ /* If we have any predefined categories, add them in as they appear. */
+ for (size_t i= 0; i < m_Config.cats.size(); i++)
+ {
+ /* Find this category and map it in if we can */
+ for (size_t j = 0; j < to_sort.size(); j++)
+ {
+ if (strcmp(m_Config.strings.GetString(m_Config.cats[i]->name),
+ m_Categories[to_sort[j]]->obj->name) == 0)
+ {
+ /* Add to the real list and remove from the temporary */
+ m_SortedCats.push_back(to_sort[j]);
+ to_sort.erase(to_sort.iterAt(j));
+ break;
+ }
+ }
+ }
+
+ /* Push any remaining items onto the unsorted list */
+ for (size_t i = 0; i < to_sort.size(); i++)
+ {
+ m_UnsortedCats.push_back(to_sort[i]);
+ }
+
+ m_bCatsNeedResort = false;
+}
+
+void TopMenu::CreatePlayers(int max_clients)
+{
+ m_max_clients = max_clients;
+ m_clients = (topmenu_player_t *)malloc(sizeof(topmenu_player_t) * (max_clients + 1));
+ memset(m_clients, 0, sizeof(topmenu_player_t) * (max_clients + 1));
+}
+
+void TopMenu::TearDownClient(topmenu_player_t *player)
+{
+ if (player->cats != NULL)
+ {
+ for (unsigned int i = 0; i < player->cat_count; i++)
+ {
+ topmenu_player_category_t *player_cat = &(player->cats[i]);
+ if (player_cat->menu != NULL)
+ {
+ player_cat->menu->Destroy();
+ }
+ }
+ delete [] player->cats;
+ }
+
+ if (player->root != NULL)
+ {
+ player->root->Destroy();
+ }
+
+ memset(player, 0, sizeof(topmenu_player_t));
+}
+
+bool TopMenu::LoadConfiguration(const char *file, char *error, size_t maxlength)
+{
+ SMCError err;
+ SMCStates states;
+
+ if ((err = textparsers->ParseFile_SMC(file, this, &states))
+ != SMCError_Okay)
+ {
+ const char *err_string = textparsers->GetSMCErrorString(err);
+ if (!err_string)
+ {
+ err_string = "Unknown";
+ }
+
+ UTIL_Format(error, maxlength, "%s", err_string);
+
+ return false;
+ }
+
+ return true;
+}
+
+bool TopMenu::OnIdentityRemoval(IdentityToken_t *owner)
+{
+ /* First sweep the categories owned by us */
+ CVector obj_list;
+ for (size_t i = 0; i < m_Categories.size(); i++)
+ {
+ if (m_Categories[i]->obj->owner == owner)
+ {
+ obj_list.push_back(m_Categories[i]->obj->object_id);
+ }
+ }
+
+ for (size_t i = 0; i < obj_list.size(); i++)
+ {
+ RemoveFromMenu(obj_list[i]);
+ }
+
+ /* Now we can look for actual items */
+ for (size_t i = 0; i < m_Objects.size(); i++)
+ {
+ if (m_Objects[i]->is_free)
+ {
+ continue;
+ }
+ if (m_Objects[i]->owner == owner)
+ {
+ assert(m_Objects[i]->type != TopMenuObject_Category);
+ RemoveFromMenu(m_Objects[i]->object_id);
+ }
+ }
+
+ return true;
+}
+
+#define PARSE_STATE_NONE 0
+#define PARSE_STATE_MAIN 1
+#define PARSE_STATE_CATEGORY 2
+unsigned int ignore_parse_level = 0;
+unsigned int current_parse_state = 0;
+config_category_t *cur_cat = NULL;
+
+void TopMenu::ReadSMC_ParseStart()
+{
+ current_parse_state = PARSE_STATE_NONE;
+ ignore_parse_level = 0;
+ cur_cat = NULL;
+
+ /* Reset the old config */
+ m_Config.strings.Reset();
+ for (size_t i = 0; i < m_Config.cats.size(); i++)
+ {
+ delete m_Config.cats[i];
+ }
+ m_Config.cats.clear();
+}
+
+SMCResult TopMenu::ReadSMC_NewSection(const SMCStates *states, const char *name)
+{
+ if (ignore_parse_level)
+ {
+ ignore_parse_level++;
+ }
+ else
+ {
+ if (current_parse_state == PARSE_STATE_NONE)
+ {
+ if (strcmp(name, "Menu") == 0)
+ {
+ current_parse_state = PARSE_STATE_MAIN;
+ }
+ else
+ {
+ ignore_parse_level = 1;
+ }
+ }
+ else if (current_parse_state == PARSE_STATE_MAIN)
+ {
+ cur_cat = new config_category_t;
+ cur_cat->name = m_Config.strings.AddString(name);
+ m_Config.cats.push_back(cur_cat);
+ current_parse_state = PARSE_STATE_CATEGORY;
+ }
+ else
+ {
+ ignore_parse_level = 1;
+ }
+ }
+
+ return SMCResult_Continue;
+}
+
+SMCResult TopMenu::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
+{
+ if (ignore_parse_level > 0
+ || current_parse_state != PARSE_STATE_CATEGORY
+ || cur_cat == NULL)
+ {
+ return SMCResult_Continue;
+ }
+
+ if (strcmp(key, "item") == 0)
+ {
+ cur_cat->commands.push_back(m_Config.strings.AddString(value));
+ }
+
+ return SMCResult_Continue;
+}
+
+SMCResult TopMenu::ReadSMC_LeavingSection(const SMCStates *states)
+{
+ if (ignore_parse_level)
+ {
+ ignore_parse_level--;
+ }
+ else
+ {
+ if (current_parse_state == PARSE_STATE_CATEGORY)
+ {
+ cur_cat = NULL;
+ current_parse_state = PARSE_STATE_MAIN;
+ }
+ else if (current_parse_state == PARSE_STATE_MAIN)
+ {
+ current_parse_state = PARSE_STATE_NONE;
+ }
+ }
+
+ return SMCResult_Continue;
+}
+
+unsigned int TopMenu::FindCategory(const char *name)
+{
+ topmenu_object_t **p_obj = m_ObjLookup.retrieve(name);
+ if (!p_obj)
+ {
+ return 0;
+ }
+
+ topmenu_object_t *obj = *p_obj;
+ if (obj->type != TopMenuObject_Category)
+ {
+ return 0;
+ }
+
+ return obj->object_id;
+}
+
+int _SortObjectNamesDescending(const void *ptr1, const void *ptr2)
+{
+ obj_by_name_t *obj1 = (obj_by_name_t *)ptr1;
+ obj_by_name_t *obj2 = (obj_by_name_t *)ptr2;
+ return strcmp(obj1->name, obj2->name);
+}
+
+unsigned int strncopy(char *dest, const char *src, size_t count)
+{
+ if (!count)
+ {
+ return 0;
+ }
+
+ char *start = dest;
+ while ((*src) && (--count))
+ {
+ *dest++ = *src++;
+ }
+ *dest = '\0';
+
+ return (dest - start);
+}
+
+size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ size_t len = vsnprintf(buffer, maxlength, fmt, ap);
+ va_end(ap);
+
+ if (len >= maxlength)
+ {
+ buffer[maxlength - 1] = '\0';
+ return (maxlength - 1);
+ }
+ else
+ {
+ return len;
+ }
+}
diff --git a/extensions/topmenus/TopMenu.h b/extensions/topmenus/TopMenu.h
index 1c80f25b..c4c0ec86 100644
--- a/extensions/topmenus/TopMenu.h
+++ b/extensions/topmenus/TopMenu.h
@@ -1,178 +1,179 @@
-/**
- * vim: set ts=4 :
- * =============================================================================
- * SourceMod Sample Extension
- * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
- * =============================================================================
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, version 3.0, as published by the
- * Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- *
- * As a special exception, AlliedModders LLC gives you permission to link the
- * code of this program (as well as its derivative works) to "Half-Life 2," the
- * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
- * by the Valve Corporation. You must obey the GNU General Public License in
- * all respects for all other code used. Additionally, AlliedModders LLC grants
- * this exception to all derivative works. AlliedModders LLC defines further
- * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
- * or .
- *
- * Version: $Id$
- */
-
-#ifndef _INCLUDE_SOURCEMOD_TOP_MENU_H_
-#define _INCLUDE_SOURCEMOD_TOP_MENU_H_
-
-#include
-#include
-#include
-#include
-#include "smsdk_ext.h"
-#include "sm_memtable.h"
-
-using namespace SourceHook;
-using namespace SourceMod;
-
-struct config_category_t
-{
- int name;
- CVector commands;
-};
-
-struct config_root_t
-{
- config_root_t() : strings(1024)
- {
- }
- BaseStringTable strings;
- CVector cats;
-};
-
-struct topmenu_object_t
-{
- char name[64]; /** Name */
- char cmdname[64]; /** Command name */
- FlagBits flags; /** Admin flags */
- ITopMenuObjectCallbacks *callbacks; /** Callbacks */
- IdentityToken_t *owner; /** Owner */
- unsigned int object_id; /** Object ID */
- topmenu_object_t *parent; /** Parent, if any */
- TopMenuObjectType type; /** Object Type */
- bool is_free; /** Free or not? */
- char info[255]; /** Info string */
-};
-
-struct topmenu_category_t
-{
- CVector obj_list; /** Full object list */
- CVector sorted; /** Sorted items */
- CVector unsorted; /** Unsorted items */
- topmenu_object_t *obj; /** Bound object */
- unsigned int serial; /** Serial number */
- bool reorder; /** Whether ordering needs updating */
-};
-
-struct topmenu_player_category_t
-{
- IBaseMenu *menu; /** menu pointer */
- unsigned int serial; /** last known serial */
-};
-
-struct topmenu_player_t
-{
- int user_id; /** userid on server */
- unsigned int menu_serial; /** menu serial no */
- IBaseMenu *root; /** root menu display */
- topmenu_player_category_t *cats; /** category display */
- unsigned int cat_count; /** number of categories */
- unsigned int last_category; /** last category they selected */
- unsigned int last_position; /** last position in that category */
- unsigned int last_root_pos; /** last page in the root menu */
-};
-
-class TopMenu :
- public ITopMenu,
- public IMenuHandler,
- public ITextListener_SMC
-{
- friend class TopMenuManager;
-public:
- TopMenu(ITopMenuObjectCallbacks *callbacks);
- ~TopMenu();
-public: //ITopMenu
- virtual unsigned int AddToMenu(const char *name,
- TopMenuObjectType type,
- ITopMenuObjectCallbacks *callbacks,
- IdentityToken_t *owner,
- const char *cmdname,
- FlagBits flags,
- unsigned int parent);
- unsigned int AddToMenu2(const char *name,
- TopMenuObjectType type,
- ITopMenuObjectCallbacks *callbacks,
- IdentityToken_t *owner,
- const char *cmdname,
- FlagBits flags,
- unsigned int parent,
- const char *info_string);
- virtual void RemoveFromMenu(unsigned int object_id);
- virtual bool DisplayMenu(int client,
- unsigned int hold_time,
- TopMenuPosition position);
- virtual bool LoadConfiguration(const char *file, char *error, size_t maxlength);
- virtual unsigned int FindCategory(const char *name);
- const char *GetObjectInfoString(unsigned int object_id);
-public: //IMenuHandler
- virtual void OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page);
- virtual void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style);
- virtual unsigned int OnMenuDisplayItem(IBaseMenu *menu,
- int client,
- IMenuPanel *panel,
- unsigned int item,
- const ItemDrawInfo &dr);
- virtual void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason);
-public: //ITextListener_SMC
- void ReadSMC_ParseStart();
- SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name);
- SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value);
- SMCResult ReadSMC_LeavingSection(const SMCStates *states);
-private:
- void SortCategoriesIfNeeded();
- void SortCategoryIfNeeded(unsigned int category);
-private:
- bool DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position);
- void CreatePlayers(int max_clients);
- void UpdateClientRoot(int client, IGamePlayer *pGamePlayer=NULL);
- void UpdateClientCategory(int client, unsigned int category);
- void TearDownClient(topmenu_player_t *player);
-private:
- void OnClientConnected(int client);
- void OnClientDisconnected(int client);
- void OnServerActivated(int max_clients);
- bool OnIdentityRemoval(IdentityToken_t *owner);
-private:
- config_root_t m_Config; /* Configuration from file */
- topmenu_player_t *m_clients; /* Client array */
- CVector m_SortedCats; /* Sorted categories */
- CVector m_UnsortedCats; /* Un-sorted categories */
- CVector m_Categories; /* Category array */
- CVector m_Objects; /* Object array */
- KTrie m_ObjLookup; /* Object lookup trie */
- unsigned int m_SerialNo; /* Serial number for updating */
- ITopMenuObjectCallbacks *m_pTitle; /* Title callbacks */
- int m_max_clients; /* Maximum number of clients */
- bool m_bCatsNeedResort; /* True if categories need a resort */
-};
-
-unsigned int strncopy(char *dest, const char *src, size_t count);
-
-#endif //_INCLUDE_SOURCEMOD_TOP_MENU_H_
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod Sample Extension
+ * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#ifndef _INCLUDE_SOURCEMOD_TOP_MENU_H_
+#define _INCLUDE_SOURCEMOD_TOP_MENU_H_
+
+#include
+#include
+#include
+#include
+#include "smsdk_ext.h"
+#include "sm_memtable.h"
+
+using namespace SourceHook;
+using namespace SourceMod;
+
+struct config_category_t
+{
+ int name;
+ CVector commands;
+};
+
+struct config_root_t
+{
+ config_root_t() : strings(1024)
+ {
+ }
+ BaseStringTable strings;
+ CVector cats;
+};
+
+struct topmenu_object_t
+{
+ char name[64]; /** Name */
+ char cmdname[64]; /** Command name */
+ FlagBits flags; /** Admin flags */
+ ITopMenuObjectCallbacks *callbacks; /** Callbacks */
+ IdentityToken_t *owner; /** Owner */
+ unsigned int object_id; /** Object ID */
+ topmenu_object_t *parent; /** Parent, if any */
+ TopMenuObjectType type; /** Object Type */
+ bool is_free; /** Free or not? */
+ char info[255]; /** Info string */
+};
+
+struct topmenu_category_t
+{
+ CVector obj_list; /** Full object list */
+ CVector sorted; /** Sorted items */
+ CVector unsorted; /** Unsorted items */
+ topmenu_object_t *obj; /** Bound object */
+ unsigned int serial; /** Serial number */
+ bool reorder; /** Whether ordering needs updating */
+};
+
+struct topmenu_player_category_t
+{
+ IBaseMenu *menu; /** menu pointer */
+ unsigned int serial; /** last known serial */
+};
+
+struct topmenu_player_t
+{
+ int user_id; /** userid on server */
+ unsigned int menu_serial; /** menu serial no */
+ IBaseMenu *root; /** root menu display */
+ topmenu_player_category_t *cats; /** category display */
+ unsigned int cat_count; /** number of categories */
+ unsigned int last_category; /** last category they selected */
+ unsigned int last_position; /** last position in that category */
+ unsigned int last_root_pos; /** last page in the root menu */
+};
+
+class TopMenu :
+ public ITopMenu,
+ public IMenuHandler,
+ public ITextListener_SMC
+{
+ friend class TopMenuManager;
+public:
+ TopMenu(ITopMenuObjectCallbacks *callbacks);
+ ~TopMenu();
+public: //ITopMenu
+ virtual unsigned int AddToMenu(const char *name,
+ TopMenuObjectType type,
+ ITopMenuObjectCallbacks *callbacks,
+ IdentityToken_t *owner,
+ const char *cmdname,
+ FlagBits flags,
+ unsigned int parent);
+ unsigned int AddToMenu2(const char *name,
+ TopMenuObjectType type,
+ ITopMenuObjectCallbacks *callbacks,
+ IdentityToken_t *owner,
+ const char *cmdname,
+ FlagBits flags,
+ unsigned int parent,
+ const char *info_string);
+ virtual void RemoveFromMenu(unsigned int object_id);
+ virtual bool DisplayMenu(int client,
+ unsigned int hold_time,
+ TopMenuPosition position);
+ virtual bool LoadConfiguration(const char *file, char *error, size_t maxlength);
+ virtual unsigned int FindCategory(const char *name);
+ const char *GetObjectInfoString(unsigned int object_id);
+ const char *GetObjectName(unsigned int object_id);
+public: //IMenuHandler
+ virtual void OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page);
+ virtual void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style);
+ virtual unsigned int OnMenuDisplayItem(IBaseMenu *menu,
+ int client,
+ IMenuPanel *panel,
+ unsigned int item,
+ const ItemDrawInfo &dr);
+ virtual void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason);
+public: //ITextListener_SMC
+ void ReadSMC_ParseStart();
+ SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name);
+ SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value);
+ SMCResult ReadSMC_LeavingSection(const SMCStates *states);
+private:
+ void SortCategoriesIfNeeded();
+ void SortCategoryIfNeeded(unsigned int category);
+private:
+ bool DisplayCategory(int client, unsigned int category, unsigned int hold_time, bool last_position);
+ void CreatePlayers(int max_clients);
+ void UpdateClientRoot(int client, IGamePlayer *pGamePlayer=NULL);
+ void UpdateClientCategory(int client, unsigned int category);
+ void TearDownClient(topmenu_player_t *player);
+private:
+ void OnClientConnected(int client);
+ void OnClientDisconnected(int client);
+ void OnServerActivated(int max_clients);
+ bool OnIdentityRemoval(IdentityToken_t *owner);
+private:
+ config_root_t m_Config; /* Configuration from file */
+ topmenu_player_t *m_clients; /* Client array */
+ CVector m_SortedCats; /* Sorted categories */
+ CVector m_UnsortedCats; /* Un-sorted categories */
+ CVector m_Categories; /* Category array */
+ CVector m_Objects; /* Object array */
+ KTrie m_ObjLookup; /* Object lookup trie */
+ unsigned int m_SerialNo; /* Serial number for updating */
+ ITopMenuObjectCallbacks *m_pTitle; /* Title callbacks */
+ int m_max_clients; /* Maximum number of clients */
+ bool m_bCatsNeedResort; /* True if categories need a resort */
+};
+
+unsigned int strncopy(char *dest, const char *src, size_t count);
+
+#endif //_INCLUDE_SOURCEMOD_TOP_MENU_H_
diff --git a/extensions/topmenus/smn_topmenus.cpp b/extensions/topmenus/smn_topmenus.cpp
index 4799b9fd..42cc17fa 100644
--- a/extensions/topmenus/smn_topmenus.cpp
+++ b/extensions/topmenus/smn_topmenus.cpp
@@ -1,358 +1,383 @@
-/**
- * vim: set ts=4 :
- * =============================================================================
- * SourceMod Sample Extension
- * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
- * =============================================================================
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, version 3.0, as published by the
- * Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- *
- * As a special exception, AlliedModders LLC gives you permission to link the
- * code of this program (as well as its derivative works) to "Half-Life 2," the
- * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
- * by the Valve Corporation. You must obey the GNU General Public License in
- * all respects for all other code used. Additionally, AlliedModders LLC grants
- * this exception to all derivative works. AlliedModders LLC defines further
- * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
- * or .
- *
- * Version: $Id$
- */
-
-#include "extension.h"
-#include "TopMenuManager.h"
-#include "TopMenu.h"
-
-HandleType_t hTopMenuType;
-
-class TopMenuHandle : public IHandleTypeDispatch
-{
-public:
- void OnHandleDestroy(HandleType_t type, void *object)
- {
- ITopMenu *pTopMenu = (ITopMenu *)object;
- g_TopMenus.DestroyTopMenu(pTopMenu);
- }
-} s_TopMenuHandle;
-
-void Initialize_Natives()
-{
- hTopMenuType = handlesys->CreateType("ITopMenu",
- &s_TopMenuHandle,
- 0,
- NULL,
- NULL,
- myself->GetIdentity(),
- NULL);
-}
-
-void Shutdown_Natives()
-{
- handlesys->RemoveType(hTopMenuType, myself->GetIdentity());
-}
-
-enum TopMenuAction
-{
- TopMenuAction_DisplayOption = 0,
- TopMenuAction_DisplayTitle = 1,
- TopMenuAction_SelectOption = 2,
- TopMenuAction_DrawOption = 3,
- TopMenuAction_RemoveObject = 4,
-};
-
-class TopMenuCallbacks : public ITopMenuObjectCallbacks
-{
-public:
- TopMenuCallbacks(IPluginFunction *pFunction) : m_pFunction(pFunction)
- {
- }
-
- unsigned int OnTopMenuDrawOption(ITopMenu *menu,
- int client,
- unsigned int object_id)
- {
- char buffer[2] = {ITEMDRAW_DEFAULT, 0x0};
- m_pFunction->PushCell(m_hMenuHandle);
- m_pFunction->PushCell(TopMenuAction_DrawOption);
- m_pFunction->PushCell(object_id);
- m_pFunction->PushCell(client);
- m_pFunction->PushStringEx(buffer, sizeof(buffer), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
- m_pFunction->PushCell(sizeof(buffer));
- m_pFunction->Execute(NULL);
- return (unsigned int)buffer[0];
- }
-
- void OnTopMenuDisplayOption(ITopMenu *menu,
- int client,
- unsigned int object_id,
- char buffer[],
- size_t maxlength)
- {
- m_pFunction->PushCell(m_hMenuHandle);
- m_pFunction->PushCell(TopMenuAction_DisplayOption);
- m_pFunction->PushCell(object_id);
- m_pFunction->PushCell(client);
- m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK);
- m_pFunction->PushCell(maxlength);
- m_pFunction->Execute(NULL);
- }
-
- void OnTopMenuDisplayTitle(ITopMenu *menu,
- int client,
- unsigned int object_id,
- char buffer[],
- size_t maxlength)
- {
- m_pFunction->PushCell(m_hMenuHandle);
- m_pFunction->PushCell(TopMenuAction_DisplayTitle);
- m_pFunction->PushCell(object_id);
- m_pFunction->PushCell(client);
- m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK);
- m_pFunction->PushCell(maxlength);
- m_pFunction->Execute(NULL);
- }
-
- void OnTopMenuSelectOption(ITopMenu *menu,
- int client,
- unsigned int object_id)
- {
- unsigned int old_reply = playerhelpers->SetReplyTo(SM_REPLY_CHAT);
- m_pFunction->PushCell(m_hMenuHandle);
- m_pFunction->PushCell(TopMenuAction_SelectOption);
- m_pFunction->PushCell(object_id);
- m_pFunction->PushCell(client);
- m_pFunction->PushString("");
- m_pFunction->PushCell(0);
- m_pFunction->Execute(NULL);
- playerhelpers->SetReplyTo(old_reply);
- }
-
- void OnTopMenuObjectRemoved(ITopMenu *menu, unsigned int object_id)
- {
- m_pFunction->PushCell(m_hMenuHandle);
- m_pFunction->PushCell(TopMenuAction_RemoveObject);
- m_pFunction->PushCell(object_id);
- m_pFunction->PushCell(0);
- m_pFunction->PushString("");
- m_pFunction->PushCell(0);
- m_pFunction->Execute(NULL);
-
- delete this;
- }
-
- Handle_t m_hMenuHandle;
- IPluginFunction *m_pFunction;
-};
-
-static cell_t CreateTopMenu(IPluginContext *pContext, const cell_t *params)
-{
- IPluginFunction *func = pContext->GetFunctionById(params[1]);
- if (func == NULL)
- {
- return pContext ->ThrowNativeError("Invalid function specified");
- }
-
- TopMenuCallbacks *cb = new TopMenuCallbacks(func);
-
- ITopMenu *pMenu = g_TopMenus.CreateTopMenu(cb);
-
- if (!pMenu)
- {
- delete cb;
- return BAD_HANDLE;
- }
-
- Handle_t hndl = handlesys->CreateHandle(hTopMenuType,
- pMenu,
- pContext->GetIdentity(),
- myself->GetIdentity(),
- NULL);
- if (hndl == 0)
- {
- g_TopMenus.DestroyTopMenu(pMenu);
- return BAD_HANDLE;
- }
-
- cb->m_hMenuHandle = hndl;
-
- return hndl;
-}
-
-static cell_t LoadTopMenuConfig(IPluginContext *pContext, const cell_t *params)
-{
- HandleError err;
- ITopMenu *pMenu;
- HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
-
- if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
- != HandleError_None)
- {
- return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
- }
-
- char *file, *err_buf;
- pContext->LocalToString(params[2], &file);
- pContext->LocalToString(params[3], &err_buf);
-
- char path[PLATFORM_MAX_PATH];
- g_pSM->BuildPath(Path_Game, path, sizeof(path), "%s", file);
-
- return pMenu->LoadConfiguration(path, err_buf, params[4]) ? 1 : 0;
-}
-
-static cell_t AddToTopMenu(IPluginContext *pContext, const cell_t *params)
-{
- HandleError err;
- ITopMenu *pMenu;
- HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
-
- if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
- != HandleError_None)
- {
- return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
- }
-
- IPluginFunction *func = pContext->GetFunctionById(params[4]);
- if (func == NULL)
- {
- return pContext ->ThrowNativeError("Invalid function specified");
- }
-
- TopMenuCallbacks *cb = new TopMenuCallbacks(func);
-
- char *name, *cmdname, *info_string = NULL;
- pContext->LocalToString(params[2], &name);
- pContext->LocalToString(params[6], &cmdname);
-
- if (params[0] >= 8)
- {
- pContext->LocalToString(params[8], &info_string);
- }
-
- TopMenuObjectType obj_type = (TopMenuObjectType)params[3];
-
- unsigned int object_id;
- if ((object_id = pMenu->AddToMenu2(name,
- obj_type,
- cb,
- pContext->GetIdentity(),
- cmdname,
- params[7],
- params[5],
- info_string)) == 0)
- {
- delete cb;
- return 0;
- }
-
- cb->m_hMenuHandle = params[1];
-
- return object_id;
-}
-
-static cell_t RemoveFromTopMenu(IPluginContext *pContext, const cell_t *params)
-{
- HandleError err;
- ITopMenu *pMenu;
- HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
-
- if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
- != HandleError_None)
- {
- return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
- }
-
- pMenu->RemoveFromMenu(params[2]);
-
- return 1;
-}
-
-static cell_t FindTopMenuCategory(IPluginContext *pContext, const cell_t *params)
-{
- HandleError err;
- ITopMenu *pMenu;
- HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
-
- if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
- != HandleError_None)
- {
- return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
- }
-
- char *name;
- pContext->LocalToString(params[2], &name);
-
- return pMenu->FindCategory(name);
-}
-
-static cell_t DisplayTopMenu(IPluginContext *pContext, const cell_t *params)
-{
- HandleError err;
- ITopMenu *pMenu;
- HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
-
- if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
- != HandleError_None)
- {
- return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
- }
-
- int client = params[2];
- IGamePlayer *player = playerhelpers->GetGamePlayer(client);
- if (!player)
- {
- return pContext->ThrowNativeError("Invalid client index %d", client);
- }
- else if (!player->IsInGame())
- {
- return pContext->ThrowNativeError("Client %d is not in game", client);
- }
-
- return pMenu->DisplayMenu(client, 0, (TopMenuPosition)params[3]);
-}
-
-static cell_t GetTopMenuInfoString(IPluginContext *pContext, const cell_t *params)
-{
- HandleError err;
- ITopMenu *pMenu;
- HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
-
- if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
- != HandleError_None)
- {
- return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
- }
-
- const char *str;
- if ((str = pMenu->GetObjectInfoString(params[2])) == NULL)
- {
- return pContext->ThrowNativeError("Invalid menu object %d", params[2]);
- }
-
- char *buffer;
- pContext->LocalToString(params[3], &buffer);
-
- return strncopy(buffer, str, params[4]);
-}
-
-sp_nativeinfo_t g_TopMenuNatives[] =
-{
- {"AddToTopMenu", AddToTopMenu},
- {"CreateTopMenu", CreateTopMenu},
- {"DisplayTopMenu", DisplayTopMenu},
- {"LoadTopMenuConfig", LoadTopMenuConfig},
- {"RemoveFromTopMenu", RemoveFromTopMenu},
- {"FindTopMenuCategory", FindTopMenuCategory},
- {"GetTopMenuInfoString", GetTopMenuInfoString},
- {NULL, NULL},
-};
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod Sample Extension
+ * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#include "extension.h"
+#include "TopMenuManager.h"
+#include "TopMenu.h"
+
+HandleType_t hTopMenuType;
+
+class TopMenuHandle : public IHandleTypeDispatch
+{
+public:
+ void OnHandleDestroy(HandleType_t type, void *object)
+ {
+ ITopMenu *pTopMenu = (ITopMenu *)object;
+ g_TopMenus.DestroyTopMenu(pTopMenu);
+ }
+} s_TopMenuHandle;
+
+void Initialize_Natives()
+{
+ hTopMenuType = handlesys->CreateType("ITopMenu",
+ &s_TopMenuHandle,
+ 0,
+ NULL,
+ NULL,
+ myself->GetIdentity(),
+ NULL);
+}
+
+void Shutdown_Natives()
+{
+ handlesys->RemoveType(hTopMenuType, myself->GetIdentity());
+}
+
+enum TopMenuAction
+{
+ TopMenuAction_DisplayOption = 0,
+ TopMenuAction_DisplayTitle = 1,
+ TopMenuAction_SelectOption = 2,
+ TopMenuAction_DrawOption = 3,
+ TopMenuAction_RemoveObject = 4,
+};
+
+class TopMenuCallbacks : public ITopMenuObjectCallbacks
+{
+public:
+ TopMenuCallbacks(IPluginFunction *pFunction) : m_pFunction(pFunction)
+ {
+ }
+
+ unsigned int OnTopMenuDrawOption(ITopMenu *menu,
+ int client,
+ unsigned int object_id)
+ {
+ char buffer[2] = {ITEMDRAW_DEFAULT, 0x0};
+ m_pFunction->PushCell(m_hMenuHandle);
+ m_pFunction->PushCell(TopMenuAction_DrawOption);
+ m_pFunction->PushCell(object_id);
+ m_pFunction->PushCell(client);
+ m_pFunction->PushStringEx(buffer, sizeof(buffer), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
+ m_pFunction->PushCell(sizeof(buffer));
+ m_pFunction->Execute(NULL);
+ return (unsigned int)buffer[0];
+ }
+
+ void OnTopMenuDisplayOption(ITopMenu *menu,
+ int client,
+ unsigned int object_id,
+ char buffer[],
+ size_t maxlength)
+ {
+ m_pFunction->PushCell(m_hMenuHandle);
+ m_pFunction->PushCell(TopMenuAction_DisplayOption);
+ m_pFunction->PushCell(object_id);
+ m_pFunction->PushCell(client);
+ m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK);
+ m_pFunction->PushCell(maxlength);
+ m_pFunction->Execute(NULL);
+ }
+
+ void OnTopMenuDisplayTitle(ITopMenu *menu,
+ int client,
+ unsigned int object_id,
+ char buffer[],
+ size_t maxlength)
+ {
+ m_pFunction->PushCell(m_hMenuHandle);
+ m_pFunction->PushCell(TopMenuAction_DisplayTitle);
+ m_pFunction->PushCell(object_id);
+ m_pFunction->PushCell(client);
+ m_pFunction->PushStringEx(buffer, maxlength, 0, SM_PARAM_COPYBACK);
+ m_pFunction->PushCell(maxlength);
+ m_pFunction->Execute(NULL);
+ }
+
+ void OnTopMenuSelectOption(ITopMenu *menu,
+ int client,
+ unsigned int object_id)
+ {
+ unsigned int old_reply = playerhelpers->SetReplyTo(SM_REPLY_CHAT);
+ m_pFunction->PushCell(m_hMenuHandle);
+ m_pFunction->PushCell(TopMenuAction_SelectOption);
+ m_pFunction->PushCell(object_id);
+ m_pFunction->PushCell(client);
+ m_pFunction->PushString("");
+ m_pFunction->PushCell(0);
+ m_pFunction->Execute(NULL);
+ playerhelpers->SetReplyTo(old_reply);
+ }
+
+ void OnTopMenuObjectRemoved(ITopMenu *menu, unsigned int object_id)
+ {
+ m_pFunction->PushCell(m_hMenuHandle);
+ m_pFunction->PushCell(TopMenuAction_RemoveObject);
+ m_pFunction->PushCell(object_id);
+ m_pFunction->PushCell(0);
+ m_pFunction->PushString("");
+ m_pFunction->PushCell(0);
+ m_pFunction->Execute(NULL);
+
+ delete this;
+ }
+
+ Handle_t m_hMenuHandle;
+ IPluginFunction *m_pFunction;
+};
+
+static cell_t CreateTopMenu(IPluginContext *pContext, const cell_t *params)
+{
+ IPluginFunction *func = pContext->GetFunctionById(params[1]);
+ if (func == NULL)
+ {
+ return pContext ->ThrowNativeError("Invalid function specified");
+ }
+
+ TopMenuCallbacks *cb = new TopMenuCallbacks(func);
+
+ ITopMenu *pMenu = g_TopMenus.CreateTopMenu(cb);
+
+ if (!pMenu)
+ {
+ delete cb;
+ return BAD_HANDLE;
+ }
+
+ Handle_t hndl = handlesys->CreateHandle(hTopMenuType,
+ pMenu,
+ pContext->GetIdentity(),
+ myself->GetIdentity(),
+ NULL);
+ if (hndl == 0)
+ {
+ g_TopMenus.DestroyTopMenu(pMenu);
+ return BAD_HANDLE;
+ }
+
+ cb->m_hMenuHandle = hndl;
+
+ return hndl;
+}
+
+static cell_t LoadTopMenuConfig(IPluginContext *pContext, const cell_t *params)
+{
+ HandleError err;
+ ITopMenu *pMenu;
+ HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+
+ if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
+ }
+
+ char *file, *err_buf;
+ pContext->LocalToString(params[2], &file);
+ pContext->LocalToString(params[3], &err_buf);
+
+ char path[PLATFORM_MAX_PATH];
+ g_pSM->BuildPath(Path_Game, path, sizeof(path), "%s", file);
+
+ return pMenu->LoadConfiguration(path, err_buf, params[4]) ? 1 : 0;
+}
+
+static cell_t AddToTopMenu(IPluginContext *pContext, const cell_t *params)
+{
+ HandleError err;
+ ITopMenu *pMenu;
+ HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+
+ if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
+ }
+
+ IPluginFunction *func = pContext->GetFunctionById(params[4]);
+ if (func == NULL)
+ {
+ return pContext ->ThrowNativeError("Invalid function specified");
+ }
+
+ TopMenuCallbacks *cb = new TopMenuCallbacks(func);
+
+ char *name, *cmdname, *info_string = NULL;
+ pContext->LocalToString(params[2], &name);
+ pContext->LocalToString(params[6], &cmdname);
+
+ if (params[0] >= 8)
+ {
+ pContext->LocalToString(params[8], &info_string);
+ }
+
+ TopMenuObjectType obj_type = (TopMenuObjectType)params[3];
+
+ unsigned int object_id;
+ if ((object_id = pMenu->AddToMenu2(name,
+ obj_type,
+ cb,
+ pContext->GetIdentity(),
+ cmdname,
+ params[7],
+ params[5],
+ info_string)) == 0)
+ {
+ delete cb;
+ return 0;
+ }
+
+ cb->m_hMenuHandle = params[1];
+
+ return object_id;
+}
+
+static cell_t RemoveFromTopMenu(IPluginContext *pContext, const cell_t *params)
+{
+ HandleError err;
+ ITopMenu *pMenu;
+ HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+
+ if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
+ }
+
+ pMenu->RemoveFromMenu(params[2]);
+
+ return 1;
+}
+
+static cell_t FindTopMenuCategory(IPluginContext *pContext, const cell_t *params)
+{
+ HandleError err;
+ ITopMenu *pMenu;
+ HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+
+ if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
+ }
+
+ char *name;
+ pContext->LocalToString(params[2], &name);
+
+ return pMenu->FindCategory(name);
+}
+
+static cell_t DisplayTopMenu(IPluginContext *pContext, const cell_t *params)
+{
+ HandleError err;
+ ITopMenu *pMenu;
+ HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+
+ if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
+ }
+
+ int client = params[2];
+ IGamePlayer *player = playerhelpers->GetGamePlayer(client);
+ if (!player)
+ {
+ return pContext->ThrowNativeError("Invalid client index %d", client);
+ }
+ else if (!player->IsInGame())
+ {
+ return pContext->ThrowNativeError("Client %d is not in game", client);
+ }
+
+ return pMenu->DisplayMenu(client, 0, (TopMenuPosition)params[3]);
+}
+
+static cell_t GetTopMenuInfoString(IPluginContext *pContext, const cell_t *params)
+{
+ HandleError err;
+ ITopMenu *pMenu;
+ HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+
+ if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
+ }
+
+ const char *str;
+ if ((str = pMenu->GetObjectInfoString(params[2])) == NULL)
+ {
+ return pContext->ThrowNativeError("Invalid menu object %d", params[2]);
+ }
+
+ char *buffer;
+ pContext->LocalToString(params[3], &buffer);
+
+ return strncopy(buffer, str, params[4]);
+}
+
+static cell_t GetTopMenuName(IPluginContext *pContext, const cell_t *params)
+{
+ HandleError err;
+ ITopMenu *pMenu;
+ HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+
+ if ((err = handlesys->ReadHandle(params[1], hTopMenuType, &sec, (void **)&pMenu))
+ != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err);
+ }
+
+ const char *str;
+ if ((str = pMenu->GetObjectName(params[2])) == NULL)
+ {
+ return pContext->ThrowNativeError("Invalid menu object %d", params[2]);
+ }
+
+ char *buffer;
+ pContext->LocalToString(params[3], &buffer);
+
+ return strncopy(buffer, str, params[4]);
+}
+
+sp_nativeinfo_t g_TopMenuNatives[] =
+{
+ {"AddToTopMenu", AddToTopMenu},
+ {"CreateTopMenu", CreateTopMenu},
+ {"DisplayTopMenu", DisplayTopMenu},
+ {"LoadTopMenuConfig", LoadTopMenuConfig},
+ {"RemoveFromTopMenu", RemoveFromTopMenu},
+ {"FindTopMenuCategory", FindTopMenuCategory},
+ {"GetTopMenuInfoString", GetTopMenuInfoString},
+ {"GetTopMenuName", GetTopMenuName},
+ {NULL, NULL},
+};
diff --git a/plugins/include/topmenus.inc b/plugins/include/topmenus.inc
index e3dc823f..049b8b62 100644
--- a/plugins/include/topmenus.inc
+++ b/plugins/include/topmenus.inc
@@ -1,277 +1,290 @@
-/**
- * vim: set ts=4 :
- * =============================================================================
- * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
- * =============================================================================
- *
- * This file is part of the SourceMod/SourcePawn SDK.
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, version 3.0, as published by the
- * Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- *
- * As a special exception, AlliedModders LLC gives you permission to link the
- * code of this program (as well as its derivative works) to "Half-Life 2," the
- * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
- * by the Valve Corporation. You must obey the GNU General Public License in
- * all respects for all other code used. Additionally, AlliedModders LLC grants
- * this exception to all derivative works. AlliedModders LLC defines further
- * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
- * or .
- *
- * Version: $Id$
- */
-
-#if defined _topmenus_included
- #endinput
-#endif
-#define _topmenus_included
-
-#include
-
-/**
- * Actions a top menu will take on an object.
- */
-enum TopMenuAction
-{
- /**
- * An option is being drawn for a menu (or for sorting purposes).
- *
- * INPUT : TopMenu Handle, object ID, client index.
- * OUTPUT: Buffer for rendering, maxlength of buffer.
- */
- TopMenuAction_DisplayOption = 0,
-
- /**
- * The title of a menu is being drawn for a given object.
- *
- * Note: The Object ID will be INVALID_TOPMENUOBJECT if drawing the
- * root title. Otherwise, the Object ID is a category.
- *
- * INPUT : TopMenu Handle, object ID, client index.
- * OUTPUT: Buffer for rendering, maxlength of buffer.
- */
- TopMenuAction_DisplayTitle = 1,
-
- /**
- * A menu option has been selected.
- *
- * The Object ID will always be an item (not a category).
- *
- * INPUT : TopMenu Handle, object ID, client index.
- */
- TopMenuAction_SelectOption = 2,
-
- /**
- * A menu option is being drawn and its flags can be overridden.
- *
- * INPUT : TopMenu Handle, object ID, client index.
- * OUTPUT: The first byte of the 'buffer' string should be set
- * to the desired flags. By default, it will contain
- * ITEMDRAW_DEFAULT.
- */
- TopMenuAction_DrawOption = 3,
-
- /**
- * Called when an object is being removed from the menu.
- * This can be used to clean up data stored in the info string.
- *
- * INPUT : TopMenu Handle, object ID.
- */
- TopMenuAction_RemoveObject = 4,
-};
-
-/**
- * Top menu object types.
- */
-enum TopMenuObjectType
-{
- TopMenuObject_Category = 0, /**< Category (sub-menu branching from root) */
- TopMenuObject_Item = 1 /**< Item on a sub-menu */
-};
-
-/**
- * Top menu starting positions for display.
- */
-enum TopMenuPosition
-{
- TopMenuPosition_Start = 0, /**< Start/root of the menu */
- TopMenuPosition_LastRoot = 1, /**< Last position in the root menu */
- TopMenuPosition_LastCategory = 3, /**< Last position in their last category */
-};
-
-/**
- * Top menu object tag for type checking.
- */
-enum TopMenuObject
-{
- INVALID_TOPMENUOBJECT = 0,
-}
-
-/**
- * TopMenu callback prototype.
- *
- * @param topmenu Handle to the TopMenu.
- * @param action TopMenuAction being performed.
- * @param object_id The object ID (if used).
- * @param param Extra parameter (if used).
- * @param buffer Output buffer (if used).
- * @param maxlength Output buffer (if used).
- * @noreturn
- */
-functag TopMenuHandler public(Handle:topmenu,
- TopMenuAction:action,
- TopMenuObject:object_id,
- param,
- String:buffer[],
- maxlength);
-
-/**
- * Creates a TopMenu.
- *
- * @param handler Handler to use for drawing the root title.
- * @return A new TopMenu Handle, or INVALID_HANDLE on failure.
- */
-native Handle:CreateTopMenu(TopMenuHandler:handler);
-
-/**
- * Re-sorts the items in a TopMenu via a configuration file.
- *
- * The format of the configuration file should be a Valve Key-Values
- * formatted file that SourceMod can parse. There should be one root
- * section, and one sub-section for each category. Each sub-section's
- * name should match the category name.
- *
- * Each sub-section may only contain key/value pairs in the form of:
- * key: "item"
- * value: Name of the item as passed to AddToTopMenu().
- *
- * The TopMenu will draw items in the order declared in the configuration
- * file. If items do not appear in the configuration file, they are sorted
- * per-player based on how the handler function renders for that player.
- * These items appear after the configuration sorted items.
- *
- * @param topmenu TopMenu Handle.
- * @param file File path.
- * @param error Error buffer.
- * @param maxlength Maximum size of the error buffer.
- * Error buffer will be filled with a
- * zero-terminated string if false is
- * returned.
- * @return True on success, false on failure.
- * @error Invalid TopMenu Handle.
- */
-native bool:LoadTopMenuConfig(Handle:topmenu, const String:file[], String:error[], maxlength);
-
-/**
- * Adds an object to a TopMenu.
- *
- * @param topmenu TopMenu Handle.
- * @param name Object name (MUST be unique).
- * @param type Object type.
- * @param handler Handler for object.
- * @param cmdname Command name (for access overrides).
- * @param flags Default access flags.
- * @param parent Parent object ID, or INVALID_TOPMENUOBJECT for none.
- * Items must have a category parent.
- * Categories must not have a parent.
- * @param info_string Arbitrary storage (max 255 bytes).
- * @return A new TopMenuObject ID, or INVALID_TOPMENUOBJECT on
- * failure.
- * @error Invalid TopMenu Handle.
- */
-native TopMenuObject:AddToTopMenu(Handle:topmenu,
- const String:name[],
- TopMenuObjectType:type,
- TopMenuHandler:handler,
- TopMenuObject:parent,
- const String:cmdname[]="",
- flags=0,
- const String:info_string[]="");
-
-/**
- * Retrieves the info string of a top menu item.
- *
- * @param topmenu TopMenu Handle.
- * @param object TopMenuObject ID.
- * @param buffer Buffer to store info string.
- * @param maxlength Maximum size of info string.
- * @return Number of bytes written, not including the
- * null terminator.
- * @error Invalid TopMenu Handle or TopMenuObject ID.
- */
-native GetTopMenuInfoString(Handle:topmenu, TopMenuObject:parent, String:buffer[], maxlength);
-
-/**
- * Removes an object from a TopMenu.
- *
- * Plugins' objects are automatically removed all TopMenus when the given
- * plugin unloads or pauses. In the case of unpausing, all items are restored.
- *
- * @param topmenu TopMenu Handle.
- * @param object TopMenuObject ID.
- * @noreturn
- * @error Invalid TopMenu Handle.
- */
-native RemoveFromTopMenu(Handle:topmenu, TopMenuObject:object);
-
-/**
- * Displays a TopMenu to a client.
- *
- * @param topmenu TopMenu Handle.
- * @param client Client index.
- * @param position Position to display from.
- * @return True on success, false on failure.
- * @error Invalid TopMenu Handle or client not in game.
- */
-native bool:DisplayTopMenu(Handle:topmenu, client, TopMenuPosition:position);
-
-/**
- * Finds a category's object ID in a TopMenu.
- *
- * @param topmenu TopMenu Handle.
- * @param name Object's unique name.
- * @return TopMenuObject ID on success, or
- * INVALID_TOPMENUOBJECT on failure.
- * @error Invalid TopMenu Handle.
- */
-native TopMenuObject:FindTopMenuCategory(Handle:topmenu, const String:name[]);
-
-/**
- * Do not edit below this line!
- */
-public Extension:__ext_topmenus =
-{
- name = "TopMenus",
- file = "topmenus.ext",
-#if defined AUTOLOAD_EXTENSIONS
- autoload = 1,
-#else
- autoload = 0,
-#endif
-#if defined REQUIRE_EXTENSIONS
- required = 1,
-#else
- required = 0,
-#endif
-};
-
-#if !defined REQUIRE_EXTENSIONS
-public __ext_topmenus_SetNTVOptional()
-{
- MarkNativeAsOptional("CreateTopMenu");
- MarkNativeAsOptional("LoadTopMenuConfig");
- MarkNativeAsOptional("AddToTopMenu");
- MarkNativeAsOptional("RemoveFromTopMenu");
- MarkNativeAsOptional("DisplayTopMenu");
- MarkNativeAsOptional("FindTopMenuCategory");
-}
-#endif
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This file is part of the SourceMod/SourcePawn SDK.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#if defined _topmenus_included
+ #endinput
+#endif
+#define _topmenus_included
+
+#include
+
+/**
+ * Actions a top menu will take on an object.
+ */
+enum TopMenuAction
+{
+ /**
+ * An option is being drawn for a menu (or for sorting purposes).
+ *
+ * INPUT : TopMenu Handle, object ID, client index.
+ * OUTPUT: Buffer for rendering, maxlength of buffer.
+ */
+ TopMenuAction_DisplayOption = 0,
+
+ /**
+ * The title of a menu is being drawn for a given object.
+ *
+ * Note: The Object ID will be INVALID_TOPMENUOBJECT if drawing the
+ * root title. Otherwise, the Object ID is a category.
+ *
+ * INPUT : TopMenu Handle, object ID, client index.
+ * OUTPUT: Buffer for rendering, maxlength of buffer.
+ */
+ TopMenuAction_DisplayTitle = 1,
+
+ /**
+ * A menu option has been selected.
+ *
+ * The Object ID will always be an item (not a category).
+ *
+ * INPUT : TopMenu Handle, object ID, client index.
+ */
+ TopMenuAction_SelectOption = 2,
+
+ /**
+ * A menu option is being drawn and its flags can be overridden.
+ *
+ * INPUT : TopMenu Handle, object ID, client index.
+ * OUTPUT: The first byte of the 'buffer' string should be set
+ * to the desired flags. By default, it will contain
+ * ITEMDRAW_DEFAULT.
+ */
+ TopMenuAction_DrawOption = 3,
+
+ /**
+ * Called when an object is being removed from the menu.
+ * This can be used to clean up data stored in the info string.
+ *
+ * INPUT : TopMenu Handle, object ID.
+ */
+ TopMenuAction_RemoveObject = 4,
+};
+
+/**
+ * Top menu object types.
+ */
+enum TopMenuObjectType
+{
+ TopMenuObject_Category = 0, /**< Category (sub-menu branching from root) */
+ TopMenuObject_Item = 1 /**< Item on a sub-menu */
+};
+
+/**
+ * Top menu starting positions for display.
+ */
+enum TopMenuPosition
+{
+ TopMenuPosition_Start = 0, /**< Start/root of the menu */
+ TopMenuPosition_LastRoot = 1, /**< Last position in the root menu */
+ TopMenuPosition_LastCategory = 3, /**< Last position in their last category */
+};
+
+/**
+ * Top menu object tag for type checking.
+ */
+enum TopMenuObject
+{
+ INVALID_TOPMENUOBJECT = 0,
+}
+
+/**
+ * TopMenu callback prototype.
+ *
+ * @param topmenu Handle to the TopMenu.
+ * @param action TopMenuAction being performed.
+ * @param object_id The object ID (if used).
+ * @param param Extra parameter (if used).
+ * @param buffer Output buffer (if used).
+ * @param maxlength Output buffer (if used).
+ * @noreturn
+ */
+functag TopMenuHandler public(Handle:topmenu,
+ TopMenuAction:action,
+ TopMenuObject:object_id,
+ param,
+ String:buffer[],
+ maxlength);
+
+/**
+ * Creates a TopMenu.
+ *
+ * @param handler Handler to use for drawing the root title.
+ * @return A new TopMenu Handle, or INVALID_HANDLE on failure.
+ */
+native Handle:CreateTopMenu(TopMenuHandler:handler);
+
+/**
+ * Re-sorts the items in a TopMenu via a configuration file.
+ *
+ * The format of the configuration file should be a Valve Key-Values
+ * formatted file that SourceMod can parse. There should be one root
+ * section, and one sub-section for each category. Each sub-section's
+ * name should match the category name.
+ *
+ * Each sub-section may only contain key/value pairs in the form of:
+ * key: "item"
+ * value: Name of the item as passed to AddToTopMenu().
+ *
+ * The TopMenu will draw items in the order declared in the configuration
+ * file. If items do not appear in the configuration file, they are sorted
+ * per-player based on how the handler function renders for that player.
+ * These items appear after the configuration sorted items.
+ *
+ * @param topmenu TopMenu Handle.
+ * @param file File path.
+ * @param error Error buffer.
+ * @param maxlength Maximum size of the error buffer.
+ * Error buffer will be filled with a
+ * zero-terminated string if false is
+ * returned.
+ * @return True on success, false on failure.
+ * @error Invalid TopMenu Handle.
+ */
+native bool:LoadTopMenuConfig(Handle:topmenu, const String:file[], String:error[], maxlength);
+
+/**
+ * Adds an object to a TopMenu.
+ *
+ * @param topmenu TopMenu Handle.
+ * @param name Object name (MUST be unique).
+ * @param type Object type.
+ * @param handler Handler for object.
+ * @param cmdname Command name (for access overrides).
+ * @param flags Default access flags.
+ * @param parent Parent object ID, or INVALID_TOPMENUOBJECT for none.
+ * Items must have a category parent.
+ * Categories must not have a parent.
+ * @param info_string Arbitrary storage (max 255 bytes).
+ * @return A new TopMenuObject ID, or INVALID_TOPMENUOBJECT on
+ * failure.
+ * @error Invalid TopMenu Handle.
+ */
+native TopMenuObject:AddToTopMenu(Handle:topmenu,
+ const String:name[],
+ TopMenuObjectType:type,
+ TopMenuHandler:handler,
+ TopMenuObject:parent,
+ const String:cmdname[]="",
+ flags=0,
+ const String:info_string[]="");
+
+/**
+ * Retrieves the info string of a top menu item.
+ *
+ * @param topmenu TopMenu Handle.
+ * @param object TopMenuObject ID.
+ * @param buffer Buffer to store info string.
+ * @param maxlength Maximum size of info string.
+ * @return Number of bytes written, not including the
+ * null terminator.
+ * @error Invalid TopMenu Handle or TopMenuObject ID.
+ */
+native GetTopMenuInfoString(Handle:topmenu, TopMenuObject:parent, String:buffer[], maxlength);
+
+/**
+ * Retrieves the name string of a top menu item.
+ *
+ * @param topmenu TopMenu Handle.
+ * @param object TopMenuObject ID.
+ * @param buffer Buffer to store info string.
+ * @param maxlength Maximum size of info string.
+ * @return Number of bytes written, not including the
+ * null terminator.
+ * @error Invalid TopMenu Handle or TopMenuObject ID.
+ */
+native GetTopMenuName(Handle:topmenu, TopMenuObject:parent, String:buffer[], maxlength);
+
+/**
+ * Removes an object from a TopMenu.
+ *
+ * Plugins' objects are automatically removed all TopMenus when the given
+ * plugin unloads or pauses. In the case of unpausing, all items are restored.
+ *
+ * @param topmenu TopMenu Handle.
+ * @param object TopMenuObject ID.
+ * @noreturn
+ * @error Invalid TopMenu Handle.
+ */
+native RemoveFromTopMenu(Handle:topmenu, TopMenuObject:object);
+
+/**
+ * Displays a TopMenu to a client.
+ *
+ * @param topmenu TopMenu Handle.
+ * @param client Client index.
+ * @param position Position to display from.
+ * @return True on success, false on failure.
+ * @error Invalid TopMenu Handle or client not in game.
+ */
+native bool:DisplayTopMenu(Handle:topmenu, client, TopMenuPosition:position);
+
+/**
+ * Finds a category's object ID in a TopMenu.
+ *
+ * @param topmenu TopMenu Handle.
+ * @param name Object's unique name.
+ * @return TopMenuObject ID on success, or
+ * INVALID_TOPMENUOBJECT on failure.
+ * @error Invalid TopMenu Handle.
+ */
+native TopMenuObject:FindTopMenuCategory(Handle:topmenu, const String:name[]);
+
+/**
+ * Do not edit below this line!
+ */
+public Extension:__ext_topmenus =
+{
+ name = "TopMenus",
+ file = "topmenus.ext",
+#if defined AUTOLOAD_EXTENSIONS
+ autoload = 1,
+#else
+ autoload = 0,
+#endif
+#if defined REQUIRE_EXTENSIONS
+ required = 1,
+#else
+ required = 0,
+#endif
+};
+
+#if !defined REQUIRE_EXTENSIONS
+public __ext_topmenus_SetNTVOptional()
+{
+ MarkNativeAsOptional("CreateTopMenu");
+ MarkNativeAsOptional("LoadTopMenuConfig");
+ MarkNativeAsOptional("AddToTopMenu");
+ MarkNativeAsOptional("RemoveFromTopMenu");
+ MarkNativeAsOptional("DisplayTopMenu");
+ MarkNativeAsOptional("FindTopMenuCategory");
+}
+#endif