diff -r 000000000000 -r b7aa2b626807 core/MenuManager.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/MenuManager.cpp Sun Mar 30 07:00:22 2008 +0000
@@ -0,0 +1,779 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod
+ * Copyright (C) 2004-2008 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
+#include "MenuManager.h"
+#include "MenuVoting.h"
+#include "sm_stringutil.h"
+#include "sourcemm_api.h"
+#include "PlayerManager.h"
+#include "MenuStyle_Valve.h"
+#include "ShareSys.h"
+#include "HandleSys.h"
+#include "sourcemm_api.h"
+
+MenuManager g_Menus;
+VoteMenuHandler s_VoteHandler;
+
+ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds");
+
+MenuManager::MenuManager()
+{
+ m_Styles.push_back(&g_ValveMenuStyle);
+ SetDefaultStyle(&g_ValveMenuStyle);
+}
+
+void MenuManager::OnSourceModAllInitialized()
+{
+ g_ShareSys.AddInterface(NULL, this);
+
+ HandleAccess access;
+ g_HandleSys.InitAccessDefaults(NULL, &access);
+
+ /* Deny cloning to menus */
+ access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
+ m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
+
+ /* Also deny deletion to styles */
+ access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
+ m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
+}
+
+void MenuManager::OnSourceModAllShutdown()
+{
+ g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
+ g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
+}
+
+void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
+{
+ if (type == m_MenuType)
+ {
+ IBaseMenu *menu = (IBaseMenu *)object;
+ menu->Destroy(false);
+ }
+ else if (type == m_StyleType)
+ {
+ /* Do nothing */
+ }
+}
+
+bool MenuManager::GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize)
+{
+ if (type == m_MenuType)
+ {
+ *pSize = ((IBaseMenu *)object)->GetApproxMemUsage();
+ return true;
+ }
+ else
+ {
+ *pSize = ((IMenuStyle *)object)->GetApproxMemUsage();
+ return true;
+ }
+
+ return false;
+}
+
+Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
+{
+ if (m_MenuType == NO_HANDLE_TYPE)
+ {
+ return BAD_HANDLE;
+ }
+
+ return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
+}
+
+Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
+{
+ if (m_StyleType == NO_HANDLE_TYPE)
+ {
+ return BAD_HANDLE;
+ }
+
+ return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
+}
+
+HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
+{
+ HandleSecurity sec;
+
+ sec.pIdentity = g_pCoreIdent;
+ sec.pOwner = NULL;
+
+ return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
+}
+
+HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
+{
+ HandleSecurity sec;
+
+ sec.pIdentity = g_pCoreIdent;
+ sec.pOwner = g_pCoreIdent;
+
+ return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
+}
+
+bool MenuManager::SetDefaultStyle(IMenuStyle *style)
+{
+ if (!style)
+ {
+ return false;
+ }
+
+ m_pDefaultStyle = style;
+
+ return true;
+}
+
+IMenuStyle *MenuManager::GetStyle(unsigned int index)
+{
+ if (index >= GetStyleCount())
+ {
+ return NULL;
+ }
+
+ return m_Styles[index];
+}
+
+void MenuManager::AddStyle(IMenuStyle *style)
+{
+ m_Styles.push_back(style);
+}
+
+unsigned int MenuManager::GetStyleCount()
+{
+ return (unsigned int)m_Styles.size();
+}
+
+IMenuStyle *MenuManager::FindStyleByName(const char *name)
+{
+ unsigned int count = GetStyleCount();
+ for (unsigned int i=0; iGetStyleName(), name) == 0)
+ {
+ return ptr;
+ }
+ }
+
+ return NULL;
+}
+
+inline bool IsSlotItem(IMenuPanel *display,
+ unsigned int style)
+{
+ if (!display->CanDrawItem(style))
+ {
+ return false;
+ }
+ if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
+ {
+ return false;
+ }
+ if (style & ITEMDRAW_RAWLINE)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
+{
+ IBaseMenu *menu = md.menu;
+
+ if (!menu)
+ {
+ return NULL;
+ }
+
+ struct
+ {
+ unsigned int position;
+ ItemDrawInfo draw;
+ } drawItems[10];
+
+ /* Figure out how many items to draw */
+ IMenuStyle *style = menu->GetDrawStyle();
+ unsigned int pgn = menu->GetPagination();
+ unsigned int maxItems = style->GetMaxPageItems();
+ bool exitButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT;
+
+ if (pgn != MENU_NO_PAGINATION)
+ {
+ maxItems = pgn;
+ }
+ else if (exitButton)
+ {
+ maxItems--;
+ }
+
+ /* This is very not allowed! */
+ if (maxItems < 2)
+ {
+ return NULL;
+ }
+
+ unsigned int totalItems = menu->GetItemCount();
+ unsigned int startItem = 0;
+
+ /* For pagination, find the starting point. */
+ if (pgn != MENU_NO_PAGINATION)
+ {
+ if (order == ItemOrder_Ascending)
+ {
+ startItem = md.lastItem;
+ /* This shouldn't happen with well-coded menus.
+ * If the item is out of bounds, switch the order to
+ * Items_Descending and make us start from the top.
+ */
+ if (startItem >= totalItems)
+ {
+ startItem = totalItems - 1;
+ order = ItemOrder_Descending;
+ }
+ }
+ else if (order == ItemOrder_Descending)
+ {
+ startItem = md.firstItem;
+ /* This shouldn't happen with well-coded menus.
+ * If searching backwards doesn't give us enough room,
+ * start from the beginning and change to ascending.
+ */
+ if (startItem <= maxItems)
+ {
+ startItem = 0;
+ order = ItemOrder_Ascending;
+ }
+ }
+ }
+
+ /* Get our Display pointer and initialize some crap */
+ IMenuPanel *panel = menu->CreatePanel();
+ IMenuHandler *mh = md.mh;
+ bool foundExtra = false;
+ unsigned int extraItem = 0;
+
+ if (panel == NULL)
+ {
+ return NULL;
+ }
+
+ /**
+ * We keep searching until:
+ * 1) There are no more items
+ * 2) We reach one OVER the maximum number of slot items
+ * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
+ */
+ unsigned int i = startItem;
+ unsigned int foundItems = 0;
+ while (totalItems)
+ {
+ ItemDrawInfo &dr = drawItems[foundItems].draw;
+ /* Is the item valid? */
+ if (menu->GetItemInfo(i, &dr) != NULL)
+ {
+ /* Ask the user to change the style, if necessary */
+ mh->OnMenuDrawItem(menu, client, i, dr.style);
+ /* Check if it's renderable */
+ if (IsSlotItem(panel, dr.style))
+ {
+ /* If we've already found the max number of items,
+ * This means we should just cancel out and log our
+ * "last item."
+ */
+ if (foundItems >= maxItems)
+ {
+ foundExtra = true;
+ extraItem = i;
+ break;
+ }
+ drawItems[foundItems++].position = i;
+ }
+ }
+ /* If there's no pagination, stop once the menu is full. */
+ if (pgn == MENU_NO_PAGINATION)
+ {
+ /* If we've filled up, then stop */
+ if (foundItems >= maxItems)
+ {
+ break;
+ }
+ }
+ /* If we're descending and this is the first item, stop */
+ if (order == ItemOrder_Descending)
+ {
+ if (i == 0)
+ {
+ break;
+ }
+ i--;
+ }
+ /* If we're ascending and this is the last item, stop */
+ else if (order == ItemOrder_Ascending)
+ {
+ if (i >= totalItems - 1)
+ {
+ break;
+ }
+ i++;
+ }
+ }
+
+ /* There were no items to draw! */
+ if (!foundItems)
+ {
+ panel->DeleteThis();
+ return NULL;
+ }
+
+ bool displayPrev = false;
+ bool displayNext = false;
+
+ /* This is an annoying process.
+ * Skip it for non-paginated menus, which get special treatment.
+ */
+ if (pgn != MENU_NO_PAGINATION)
+ {
+ if (foundExtra)
+ {
+ if (order == ItemOrder_Descending)
+ {
+ displayPrev = true;
+ md.firstItem = extraItem;
+ }
+ else if (order == ItemOrder_Ascending)
+ {
+ displayNext = true;
+ md.lastItem = extraItem;
+ }
+ }
+
+ unsigned int lastItem = 0;
+ ItemDrawInfo dr;
+ /* Find the last feasible item to search from. */
+ if (order == ItemOrder_Descending)
+ {
+ lastItem = drawItems[0].position;
+ if (lastItem >= totalItems - 1)
+ {
+ goto skip_search;
+ }
+ while (++lastItem < totalItems)
+ {
+ if (menu->GetItemInfo(lastItem, &dr) != NULL)
+ {
+ mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
+ if (IsSlotItem(panel, dr.style))
+ {
+ displayNext = true;
+ md.lastItem = lastItem;
+ break;
+ }
+ }
+ }
+ }
+ else if (order == ItemOrder_Ascending)
+ {
+ lastItem = drawItems[0].position;
+ if (lastItem == 0)
+ {
+ goto skip_search;
+ }
+ lastItem--;
+ while (lastItem != 0)
+ {
+ if (menu->GetItemInfo(lastItem, &dr) != NULL)
+ {
+ mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
+ if (IsSlotItem(panel, dr.style))
+ {
+ displayPrev = true;
+ md.firstItem = lastItem;
+ break;
+ }
+ }
+ lastItem--;
+ }
+ }
+ }
+
+skip_search:
+
+ /* Draw the item according to the order */
+ menu_slots_t *slots = md.slots;
+ unsigned int position = 0; /* Keep track of the last position */
+ if (order == ItemOrder_Ascending)
+ {
+ md.item_on_page = drawItems[0].position;
+ for (unsigned int i = 0; i < foundItems; i++)
+ {
+ ItemDrawInfo &dr = drawItems[i].draw;
+ if ((position = mh->OnMenuDisplayItem(menu, client, panel, drawItems[i].position, dr)) == 0)
+ {
+ position = panel->DrawItem(dr);
+ }
+ if (position != 0)
+ {
+ slots[position].item = drawItems[i].position;
+ if ((dr.style & ITEMDRAW_DISABLED) == ITEMDRAW_DISABLED)
+ {
+ slots[position].type = ItemSel_None;
+ }
+ else
+ {
+ slots[position].type = ItemSel_Item;
+ }
+ }
+ }
+ }
+ else if (order == ItemOrder_Descending)
+ {
+ unsigned int i = foundItems;
+ /* NOTE: There will always be at least one item because
+ * of the check earlier.
+ */
+ md.item_on_page = drawItems[foundItems - 1].position;
+ while (i--)
+ {
+ ItemDrawInfo &dr = drawItems[i].draw;
+ if ((position = mh->OnMenuDisplayItem(menu, client, panel, drawItems[i].position, dr)) == 0)
+ {
+ position = panel->DrawItem(dr);
+ }
+ if (position != 0)
+ {
+ slots[position].item = drawItems[i].position;
+ if ((dr.style & ITEMDRAW_DISABLED) == ITEMDRAW_DISABLED)
+ {
+ slots[position].type = ItemSel_None;
+ }
+ else
+ {
+ slots[position].type = ItemSel_Item;
+ }
+ }
+ }
+ }
+
+ /* Now, we need to check if we need to add anything extra */
+ if (pgn != MENU_NO_PAGINATION || exitButton)
+ {
+ bool canDrawDisabled = panel->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
+ bool exitBackButton = false;
+ char text[50];
+
+ if (pgn != MENU_NO_PAGINATION
+ && (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXITBACK)
+ {
+ exitBackButton = true;
+ }
+
+ /* Calculate how many items we are allowed for control stuff */
+ unsigned int padding = style->GetMaxPageItems() - maxItems;
+
+ /* Add the number of available slots */
+ padding += (maxItems - foundItems);
+
+ /* Someday, if we are able to re-enable this, we will be very lucky men. */
+#if 0
+ if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
+ {
+#endif
+ /* Even if we don't draw an exit button, we invalidate the slot. */
+ padding--;
+#if 0
+ } else {
+ /* Otherwise, we don't draw anything and leave the slot available */
+ exitButton = false;
+ }
+#endif
+
+ if (pgn != MENU_NO_PAGINATION)
+ {
+ /* Subtract two slots for the displayNext/displayPrev padding */
+ padding -= 2;
+ }
+
+ /* If we have an "Exit Back" button and the space to draw it, do so. */
+ if (exitBackButton)
+ {
+ if (!displayPrev)
+ {
+ displayPrev = true;
+ }
+ else
+ {
+ exitBackButton = false;
+ }
+ }
+
+ /**
+ * We allow next/prev to be undrawn if neither exists.
+ * Thus, we only need padding if one of them will be drawn,
+ * or the exit button will be drawn.
+ */
+ ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
+ if (exitButton || (displayNext || displayPrev))
+ {
+ /* If there are no control options,
+ * Instead just pad with invisible slots.
+ */
+ if (!displayPrev && !displayPrev)
+ {
+ padItem.style = ITEMDRAW_NOTEXT;
+ }
+ /* Add spacers so we can pad to the end */
+ for (unsigned int i=0; iDrawItem(padItem);
+ slots[position].type = ItemSel_None;
+ }
+ }
+
+ /* Put a fake spacer before control stuff, if possible */
+ if ((displayPrev || displayNext) || exitButton)
+ {
+ ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
+ panel->DrawItem(draw);
+ }
+
+ ItemDrawInfo dr(text, 0);
+
+ /**
+ * If we have one or the other, we need to have spacers for both.
+ */
+ if (pgn != MENU_NO_PAGINATION)
+ {
+ if (displayPrev || displayNext)
+ {
+ /* PREVIOUS */
+ ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
+ if (displayPrev || canDrawDisabled)
+ {
+ if (exitBackButton)
+ {
+ CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
+ dr.style = ITEMDRAW_CONTROL;
+ position = panel->DrawItem(dr);
+ slots[position].type = ItemSel_ExitBack;
+ }
+ else
+ {
+ CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL);
+ dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
+ position = panel->DrawItem(dr);
+ slots[position].type = ItemSel_Back;
+ }
+ }
+ else if (displayNext || exitButton)
+ {
+ /* If we can't display this, and there is an exit button,
+ * we need to pad!
+ */
+ position = panel->DrawItem(padCtrlItem);
+ slots[position].type = ItemSel_None;
+ }
+
+ /* NEXT */
+ if (displayNext || canDrawDisabled)
+ {
+ CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
+ dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
+ position = panel->DrawItem(dr);
+ slots[position].type = ItemSel_Next;
+ }
+ else if (exitButton)
+ {
+ /* If we can't display this,
+ * but there is an "exit" button, we need to pad!
+ */
+ position = panel->DrawItem(padCtrlItem);
+ slots[position].type = ItemSel_None;
+ }
+ }
+ else
+ {
+ /* Otherwise, bump to two slots! */
+ ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
+ position = panel->DrawItem(numBump);
+ slots[position].type = ItemSel_None;
+ position = panel->DrawItem(numBump);
+ slots[position].type = ItemSel_None;
+ }
+ }
+
+ /* EXIT */
+ if (exitButton)
+ {
+ CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
+ dr.style = ITEMDRAW_CONTROL;
+ position = panel->DrawItem(dr);
+ slots[position].type = ItemSel_Exit;
+ }
+ }
+
+ /* Lastly, fill in any slots we could have missed */
+ for (unsigned int i = position + 1; i < 10; i++)
+ {
+ slots[i].type = ItemSel_None;
+ }
+
+ /* Do title stuff */
+ mh->OnMenuDisplay(menu, client, panel);
+ panel->DrawTitle(menu->GetDefaultTitle(), true);
+
+ return panel;
+}
+
+IMenuStyle *MenuManager::GetDefaultStyle()
+{
+ return m_pDefaultStyle;
+}
+
+bool MenuManager::MenuSoundsEnabled()
+{
+ return (sm_menu_sounds.GetInt() != 0);
+}
+
+ConfigResult MenuManager::OnSourceModConfigChanged(const char *key,
+ const char *value,
+ ConfigSource source,
+ char *error,
+ size_t maxlength)
+{
+ if (strcmp(key, "MenuItemSound") == 0)
+ {
+ m_SelectSound.assign(value);
+ return ConfigResult_Accept;
+ } else if (strcmp(key, "MenuExitBackSound") == 0) {
+ m_ExitBackSound.assign(value);
+ return ConfigResult_Accept;
+ } else if (strcmp(key, "MenuExitSound") == 0) {
+ m_ExitSound.assign(value);
+ return ConfigResult_Accept;
+ }
+
+ return ConfigResult_Ignore;
+}
+
+const char *MenuManager::GetMenuSound(ItemSelection sel)
+{
+ const char *sound = NULL;
+
+ switch (sel)
+ {
+ case ItemSel_Back:
+ case ItemSel_Next:
+ case ItemSel_Item:
+ {
+ if (m_SelectSound.size() > 0)
+ {
+ sound = m_SelectSound.c_str();
+ }
+ break;
+ }
+ case ItemSel_ExitBack:
+ {
+ if (m_ExitBackSound.size() > 0)
+ {
+ sound = m_ExitBackSound.c_str();
+ }
+ break;
+ }
+ case ItemSel_Exit:
+ {
+ if (m_ExitSound.size() > 0)
+ {
+ sound = m_ExitSound.c_str();
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ return sound;
+}
+
+void MenuManager::OnSourceModLevelChange(const char *mapName)
+{
+ if (m_SelectSound.size() > 0)
+ {
+ enginesound->PrecacheSound(m_SelectSound.c_str(), true);
+ }
+ if (m_ExitBackSound.size() > 0)
+ {
+ enginesound->PrecacheSound(m_ExitBackSound.c_str(), true);
+ }
+ if (m_ExitSound.size() > 0)
+ {
+ enginesound->PrecacheSound(m_ExitSound.c_str(), true);
+ }
+}
+
+void MenuManager::CancelMenu(IBaseMenu *menu)
+{
+ if (s_VoteHandler.GetCurrentMenu() == menu
+ && !s_VoteHandler.IsCancelling())
+ {
+ s_VoteHandler.CancelVoting();
+ return;
+ }
+
+ menu->Cancel();
+}
+
+bool MenuManager::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags)
+{
+ return s_VoteHandler.StartVote(menu, num_clients, clients, max_time, flags);
+}
+
+bool MenuManager::IsVoteInProgress()
+{
+ return s_VoteHandler.IsVoteInProgress();
+}
+
+void MenuManager::CancelVoting()
+{
+ s_VoteHandler.CancelVoting();
+}
+
+unsigned int MenuManager::GetRemainingVoteDelay()
+{
+ return s_VoteHandler.GetRemainingVoteDelay();
+}