3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, version 3.0, as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to link the
21 * code of this program (as well as its derivative works) to "Half-Life 2," the
22 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 * by the Valve Corporation. You must obey the GNU General Public License in
24 * all respects for all other code used. Additionally, AlliedModders LLC grants
25 * this exception to all derivative works. AlliedModders LLC defines further
26 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 * or <http://www.sourcemod.net/license.php>.
35 #include "MenuManager.h"
36 #include "MenuVoting.h"
37 #include "sm_stringutil.h"
38 #include "sourcemm_api.h"
39 #include "PlayerManager.h"
40 #include "MenuStyle_Valve.h"
41 #include "sourcemm_api.h"
42 #include "logic_bridge.h"
45 VoteMenuHandler s_VoteHandler;
47 ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds");
49 MenuManager::MenuManager()
51 m_Styles.push_back(&g_ValveMenuStyle);
52 SetDefaultStyle(&g_ValveMenuStyle);
55 void MenuManager::OnSourceModAllInitialized()
57 sharesys->AddInterface(NULL, this);
60 handlesys->InitAccessDefaults(NULL, &access);
62 /* Deny cloning to menus */
63 access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
64 m_MenuType = handlesys->CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
66 /* Also deny deletion to styles */
67 access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
68 m_StyleType = handlesys->CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
71 void MenuManager::OnSourceModAllShutdown()
73 handlesys->RemoveType(m_MenuType, g_pCoreIdent);
74 handlesys->RemoveType(m_StyleType, g_pCoreIdent);
77 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
79 if (type == m_MenuType)
81 IBaseMenu *menu = (IBaseMenu *)object;
84 else if (type == m_StyleType)
90 bool MenuManager::GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize)
92 if (type == m_MenuType)
94 *pSize = ((IBaseMenu *)object)->GetApproxMemUsage();
99 *pSize = ((IMenuStyle *)object)->GetApproxMemUsage();
106 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
108 if (m_MenuType == NO_HANDLE_TYPE)
113 return handlesys->CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
116 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
118 if (m_StyleType == NO_HANDLE_TYPE)
123 return handlesys->CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
126 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
130 sec.pIdentity = g_pCoreIdent;
133 return handlesys->ReadHandle(handle, m_MenuType, &sec, (void **)menu);
136 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
140 sec.pIdentity = g_pCoreIdent;
141 sec.pOwner = g_pCoreIdent;
143 return handlesys->ReadHandle(handle, m_StyleType, &sec, (void **)style);
146 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
153 m_pDefaultStyle = style;
158 IMenuStyle *MenuManager::GetStyle(unsigned int index)
160 if (index >= GetStyleCount())
165 return m_Styles[index];
168 void MenuManager::AddStyle(IMenuStyle *style)
170 m_Styles.push_back(style);
173 unsigned int MenuManager::GetStyleCount()
175 return (unsigned int)m_Styles.size();
178 IMenuStyle *MenuManager::FindStyleByName(const char *name)
180 unsigned int count = GetStyleCount();
181 for (unsigned int i=0; i<count; i++)
183 IMenuStyle *ptr = GetStyle(i);
184 if (strcasecmp(ptr->GetStyleName(), name) == 0)
193 inline bool IsSlotItem(IMenuPanel *display,
196 if (!display->CanDrawItem(style))
200 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
204 if (style & ITEMDRAW_RAWLINE)
212 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
214 IBaseMenu *menu = md.menu;
223 unsigned int position;
227 /* Figure out how many items to draw */
228 IMenuStyle *style = menu->GetDrawStyle();
229 unsigned int pgn = menu->GetPagination();
230 unsigned int maxItems = style->GetMaxPageItems();
231 bool exitButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT;
232 bool novoteButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_NOVOTE) == MENUFLAG_BUTTON_NOVOTE;
234 if (pgn != MENU_NO_PAGINATION)
248 /* This is very not allowed! */
254 unsigned int totalItems = menu->GetItemCount();
255 unsigned int startItem = 0;
257 /* For pagination, find the starting point. */
258 if (pgn != MENU_NO_PAGINATION)
260 if (order == ItemOrder_Ascending)
262 startItem = md.lastItem;
263 /* This shouldn't happen with well-coded menus.
264 * If the item is out of bounds, switch the order to
265 * Items_Descending and make us start from the top.
267 if (startItem >= totalItems)
269 startItem = totalItems - 1;
270 order = ItemOrder_Descending;
273 else if (order == ItemOrder_Descending)
275 startItem = md.firstItem;
276 /* This shouldn't happen with well-coded menus.
277 * If searching backwards doesn't give us enough room,
278 * start from the beginning and change to ascending.
280 if (startItem <= maxItems)
283 order = ItemOrder_Ascending;
288 /* Get our Display pointer and initialize some crap */
289 IMenuPanel *panel = menu->CreatePanel();
290 IMenuHandler *mh = md.mh;
291 bool foundExtra = false;
292 unsigned int extraItem = 0;
300 * We keep searching until:
301 * 1) There are no more items
302 * 2) We reach one OVER the maximum number of slot items
303 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
305 unsigned int i = startItem;
306 unsigned int foundItems = 0;
309 ItemDrawInfo &dr = drawItems[foundItems].draw;
310 /* Is the item valid? */
311 if (menu->GetItemInfo(i, &dr) != NULL)
313 /* Ask the user to change the style, if necessary */
314 mh->OnMenuDrawItem(menu, client, i, dr.style);
315 /* Check if it's renderable */
316 if (IsSlotItem(panel, dr.style))
318 /* If we've already found the max number of items,
319 * This means we should just cancel out and log our
322 if (foundItems >= maxItems)
328 drawItems[foundItems++].position = i;
331 /* If there's no pagination, stop once the menu is full. */
332 if (pgn == MENU_NO_PAGINATION)
334 /* If we've filled up, then stop */
335 if (foundItems >= maxItems)
340 /* If we're descending and this is the first item, stop */
341 if (order == ItemOrder_Descending)
349 /* If we're ascending and this is the last item, stop */
350 else if (order == ItemOrder_Ascending)
352 if (i >= totalItems - 1)
360 /* There were no items to draw! */
367 bool displayPrev = false;
368 bool displayNext = false;
370 /* This is an annoying process.
371 * Skip it for non-paginated menus, which get special treatment.
373 if (pgn != MENU_NO_PAGINATION)
377 if (order == ItemOrder_Descending)
380 md.firstItem = extraItem;
382 else if (order == ItemOrder_Ascending)
385 md.lastItem = extraItem;
389 unsigned int lastItem = 0;
391 /* Find the last feasible item to search from. */
392 if (order == ItemOrder_Descending)
394 lastItem = drawItems[0].position;
395 if (lastItem >= totalItems - 1)
399 while (++lastItem < totalItems)
401 if (menu->GetItemInfo(lastItem, &dr) != NULL)
403 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
404 if (IsSlotItem(panel, dr.style))
407 md.lastItem = lastItem;
413 else if (order == ItemOrder_Ascending)
415 lastItem = drawItems[0].position;
421 while (lastItem != 0)
423 if (menu->GetItemInfo(lastItem, &dr) != NULL)
425 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
426 if (IsSlotItem(panel, dr.style))
429 md.firstItem = lastItem;
440 /* Draw the item according to the order */
441 menu_slots_t *slots = md.slots;
442 unsigned int position = 0; /* Keep track of the last position */
447 if (!logicore.CoreTranslate(text, sizeof(text), "%T", 2, NULL, "No Vote", &client))
449 UTIL_Format(text, sizeof(text), "No Vote");
451 ItemDrawInfo dr(text, 0);
452 position = panel->DrawItem(dr);
453 slots[position].type = ItemSel_Exit;
457 if (order == ItemOrder_Ascending)
459 md.item_on_page = drawItems[0].position;
460 for (unsigned int i = 0; i < foundItems; i++)
462 ItemDrawInfo &dr = drawItems[i].draw;
463 if ((position = mh->OnMenuDisplayItem(menu, client, panel, drawItems[i].position, dr)) == 0)
465 position = panel->DrawItem(dr);
469 slots[position].item = drawItems[i].position;
470 if ((dr.style & ITEMDRAW_DISABLED) == ITEMDRAW_DISABLED)
472 slots[position].type = ItemSel_None;
476 slots[position].type = ItemSel_Item;
481 else if (order == ItemOrder_Descending)
483 unsigned int i = foundItems;
484 /* NOTE: There will always be at least one item because
485 * of the check earlier.
487 md.item_on_page = drawItems[foundItems - 1].position;
490 ItemDrawInfo &dr = drawItems[i].draw;
491 if ((position = mh->OnMenuDisplayItem(menu, client, panel, drawItems[i].position, dr)) == 0)
493 position = panel->DrawItem(dr);
497 slots[position].item = drawItems[i].position;
498 if ((dr.style & ITEMDRAW_DISABLED) == ITEMDRAW_DISABLED)
500 slots[position].type = ItemSel_None;
504 slots[position].type = ItemSel_Item;
510 /* Now, we need to check if we need to add anything extra */
511 if (pgn != MENU_NO_PAGINATION || exitButton)
513 bool canDrawDisabled = panel->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
514 bool exitBackButton = false;
517 if (pgn != MENU_NO_PAGINATION
518 && (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXITBACK)
520 exitBackButton = true;
523 /* Calculate how many items we are allowed for control stuff */
524 unsigned int padding = style->GetMaxPageItems() - maxItems;
526 /* Add the number of available slots */
527 padding += (maxItems - foundItems);
529 /* Someday, if we are able to re-enable this, we will be very lucky men. */
531 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
534 /* Even if we don't draw an exit button, we invalidate the slot. */
538 /* Otherwise, we don't draw anything and leave the slot available */
543 if (pgn != MENU_NO_PAGINATION)
545 /* Subtract two slots for the displayNext/displayPrev padding */
549 /* If we have an "Exit Back" button and the space to draw it, do so. */
558 exitBackButton = false;
563 * We allow next/prev to be undrawn if neither exists.
564 * Thus, we only need padding if one of them will be drawn,
565 * or the exit button will be drawn.
567 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
568 if (exitButton || (displayNext || displayPrev))
570 /* If there are no control options,
571 * Instead just pad with invisible slots.
573 if (!displayPrev && !displayPrev)
575 padItem.style = ITEMDRAW_NOTEXT;
577 /* Add spacers so we can pad to the end */
578 for (unsigned int i=0; i<padding; i++)
580 position = panel->DrawItem(padItem);
581 slots[position].type = ItemSel_None;
585 /* Put a fake spacer before control stuff, if possible */
586 if ((displayPrev || displayNext) || exitButton)
588 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
589 panel->DrawItem(draw);
592 ItemDrawInfo dr(text, 0);
595 * If we have one or the other, we need to have spacers for both.
597 if (pgn != MENU_NO_PAGINATION)
599 if (displayPrev || displayNext)
602 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
603 if (displayPrev || canDrawDisabled)
607 if (!logicore.CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Back", &client))
609 UTIL_Format(text, sizeof(text), "Back");
611 dr.style = ITEMDRAW_CONTROL;
612 position = panel->DrawItem(dr);
613 slots[position].type = ItemSel_ExitBack;
617 if (!logicore.CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Previous", &client))
619 UTIL_Format(text, sizeof(text), "Previous");
621 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
622 position = panel->DrawItem(dr);
623 slots[position].type = ItemSel_Back;
626 else if (displayNext || exitButton)
628 /* If we can't display this, and there is an exit button,
631 position = panel->DrawItem(padCtrlItem);
632 slots[position].type = ItemSel_None;
636 if (displayNext || canDrawDisabled)
638 if (!logicore.CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Next", &client))
640 UTIL_Format(text, sizeof(text), "Next");
642 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
643 position = panel->DrawItem(dr);
644 slots[position].type = ItemSel_Next;
648 /* If we can't display this,
649 * but there is an "exit" button, we need to pad!
651 position = panel->DrawItem(padCtrlItem);
652 slots[position].type = ItemSel_None;
657 /* Otherwise, bump to two slots! */
658 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
659 position = panel->DrawItem(numBump);
660 slots[position].type = ItemSel_None;
661 position = panel->DrawItem(numBump);
662 slots[position].type = ItemSel_None;
669 if (!logicore.CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Exit", &client))
671 UTIL_Format(text, sizeof(text), "Exit");
673 dr.style = ITEMDRAW_CONTROL;
674 position = panel->DrawItem(dr);
675 slots[position].type = ItemSel_Exit;
679 /* Lastly, fill in any slots we could have missed */
680 for (unsigned int i = position + 1; i < 10; i++)
682 slots[i].type = ItemSel_None;
686 mh->OnMenuDisplay(menu, client, panel);
687 panel->DrawTitle(menu->GetDefaultTitle(), true);
692 IMenuStyle *MenuManager::GetDefaultStyle()
694 return m_pDefaultStyle;
697 bool MenuManager::MenuSoundsEnabled()
699 return (sm_menu_sounds.GetInt() != 0);
702 ConfigResult MenuManager::OnSourceModConfigChanged(const char *key,
708 if (strcmp(key, "MenuItemSound") == 0)
710 m_SelectSound.assign(value);
711 return ConfigResult_Accept;
712 } else if (strcmp(key, "MenuExitBackSound") == 0) {
713 m_ExitBackSound.assign(value);
714 return ConfigResult_Accept;
715 } else if (strcmp(key, "MenuExitSound") == 0) {
716 m_ExitSound.assign(value);
717 return ConfigResult_Accept;
720 return ConfigResult_Ignore;
723 const char *MenuManager::GetMenuSound(ItemSelection sel)
725 const char *sound = NULL;
733 if (m_SelectSound.size() > 0)
735 sound = m_SelectSound.c_str();
739 case ItemSel_ExitBack:
741 if (m_ExitBackSound.size() > 0)
743 sound = m_ExitBackSound.c_str();
749 if (m_ExitSound.size() > 0)
751 sound = m_ExitSound.c_str();
764 void MenuManager::OnSourceModLevelChange(const char *mapName)
766 if (m_SelectSound.size() > 0)
768 enginesound->PrecacheSound(m_SelectSound.c_str(), true);
770 if (m_ExitBackSound.size() > 0)
772 enginesound->PrecacheSound(m_ExitBackSound.c_str(), true);
774 if (m_ExitSound.size() > 0)
776 enginesound->PrecacheSound(m_ExitSound.c_str(), true);
780 void MenuManager::CancelMenu(IBaseMenu *menu)
782 if (s_VoteHandler.GetCurrentMenu() == menu
783 && !s_VoteHandler.IsCancelling())
785 s_VoteHandler.CancelVoting();
792 bool MenuManager::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags)
794 return s_VoteHandler.StartVote(menu, num_clients, clients, max_time, flags);
797 bool MenuManager::IsVoteInProgress()
799 return s_VoteHandler.IsVoteInProgress();
802 void MenuManager::CancelVoting()
804 s_VoteHandler.CancelVoting();
807 unsigned int MenuManager::GetRemainingVoteDelay()
809 return s_VoteHandler.GetRemainingVoteDelay();
812 bool MenuManager::IsClientInVotePool(int client)
814 return s_VoteHandler.IsClientInVotePool(client);
817 bool MenuManager::RedrawClientVoteMenu(int client)
819 return RedrawClientVoteMenu2(client, true);
822 bool MenuManager::RedrawClientVoteMenu2(int client, bool revote)
824 return s_VoteHandler.RedrawToClient(client, revote);