/** * 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" #include "Translator.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) { if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Back", &client)) { UTIL_Format(text, sizeof(text), "Back"); } dr.style = ITEMDRAW_CONTROL; position = panel->DrawItem(dr); slots[position].type = ItemSel_ExitBack; } else { if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Previous", &client)) { UTIL_Format(text, sizeof(text), "Previous"); } 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) { if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Next", &client)) { UTIL_Format(text, sizeof(text), "Next"); } 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) { if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Exit", &client)) { UTIL_Format(text, sizeof(text), "Exit"); } 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(); } bool MenuManager::IsClientInVotePool(int client) { return s_VoteHandler.IsClientInVotePool(client); } bool MenuManager::RedrawClientVoteMenu(int client) { return RedrawClientVoteMenu2(client, true); } bool MenuManager::RedrawClientVoteMenu2(int client, bool revote) { return s_VoteHandler.RedrawToClient(client, revote); }