From d04d0e408960b43642a11675626e2ad6b79798c3 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 20 Nov 2007 02:22:26 +0000 Subject: [PATCH] submitting patch from amb1181 - retrieving topmenu object name --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401705 --- extensions/topmenus/TopMenu.cpp | 2224 +++++++++++++------------- extensions/topmenus/TopMenu.h | 357 ++--- extensions/topmenus/smn_topmenus.cpp | 741 ++++----- plugins/include/topmenus.inc | 567 +++---- 4 files changed, 1971 insertions(+), 1918 deletions(-) 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