3 * ===============================================================
4 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
5 * ===============================================================
7 * This file is not open source and may not be copied without explicit
8 * written permission of AlliedModders LLC. This file may not be redistributed
9 * in whole or significant part.
10 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
18 #include "MenuManager.h"
19 #include "sm_stringutil.h"
20 #include "sourcemm_api.h"
21 #include "PlayerManager.h"
22 #include "MenuStyle_Valve.h"
24 #include "HandleSys.h"
25 #include "sourcemm_api.h"
29 ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds");
31 /*******************************
32 *******************************
33 ******** VOTE HANDLER *********
34 *******************************
35 *******************************/
37 unsigned int VoteMenuHandler::GetMenuAPIVersion2()
39 return m_pHandler->GetMenuAPIVersion2();
42 bool VoteMenuHandler::IsVoteInProgress()
44 return (m_pCurMenu != NULL);
47 void VoteMenuHandler::InitializeVoting(IBaseMenu *menu)
49 m_Items = menu->GetItemCount();
51 if (m_Votes.size() < (size_t)m_Items)
53 /* Only clear the items we need to... */
54 size_t size = m_Votes.size();
55 for (size_t i=0; i<size; i++)
59 m_Votes.resize(m_Items, 0);
61 for (unsigned int i=0; i<m_Items; i++)
69 m_pHandler->OnMenuStart(m_pCurMenu);
72 void VoteMenuHandler::StartVoting()
76 m_pHandler->OnMenuVoteStart(m_pCurMenu);
78 /* By now we know how many clients were set.
79 * If there are none, we should end IMMEDIATELY.
87 void VoteMenuHandler::DecrementPlayerCount()
89 assert(m_Clients > 0);
93 if (m_bStarted && m_Clients == 0)
99 void VoteMenuHandler::EndVoting()
103 /* If we were cancelled, don't bother tabulating anything.
104 * Reset just in case someone tries to redraw, which means
105 * we need to save our states.
107 IBaseMenu *menu = m_pCurMenu;
109 m_pHandler->OnMenuVoteCancel(menu);
110 m_pHandler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
114 unsigned int chosen = 0;
115 unsigned int highest = 0;
116 unsigned int dup_count = 0;
117 unsigned int total = m_Votes.size() ? m_Votes[0] : 0;
119 /* If we got zero votes, take a shortcut. */
122 /* Pick a random item and then jump far, far away. */
123 srand((unsigned int)(time(NULL)));
124 chosen = (unsigned int)rand() % m_Items;
128 /* We can't have more dups than this!
129 * This is the max number of players.
131 unsigned int dup_array[256];
133 for (size_t i=1; i<m_Items; i++)
135 if (m_Votes[i] > m_Votes[highest])
137 /* If we have a new highest count, mark it and trash the duplicate
138 * list by setting the total to 0.
142 } else if (m_Votes[i] == m_Votes[highest]) {
143 /* If they're equal, mark it in the duplicate list.
144 * We'll add in the original later.
146 dup_array[dup_count++] = i;
151 /* Check if we need to pick from the duplicate list */
154 /* Re-add the original to the list because it's not in there. */
155 dup_array[dup_count++] = highest;
157 /* Pick a random slot. */
158 srand((unsigned int)(time(NULL)));
159 unsigned int r = (unsigned int)rand() % dup_count;
162 chosen = dup_array[r];
168 m_pHandler->OnMenuVoteEnd(m_pCurMenu, chosen, m_Votes[highest], total);
169 m_pHandler->OnMenuEnd(m_pCurMenu, MenuEnd_VotingDone);
173 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
178 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
180 DecrementPlayerCount();
183 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
185 m_pHandler->OnMenuCancel(menu, client, reason);
188 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
190 m_pHandler->OnMenuDisplay(menu, client, display);
193 unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
195 return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
198 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
200 m_pHandler->OnMenuDrawItem(menu, client, item, style);
203 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
205 /* Check by our item count, NOT the vote array size */
212 m_pHandler->OnMenuSelect(menu, client, item);
215 void VoteMenuHandler::Reset(IMenuHandler *mh)
221 void VoteMenuHandler::InternalReset()
228 m_bCancelled = false;
231 void VoteMenuHandler::CancelVoting()
236 /*******************************
237 *******************************
238 ******** MENU MANAGER *********
239 *******************************
240 *******************************/
242 MenuManager::MenuManager()
244 m_Styles.push_back(&g_ValveMenuStyle);
245 SetDefaultStyle(&g_ValveMenuStyle);
248 void MenuManager::OnSourceModAllInitialized()
250 g_ShareSys.AddInterface(NULL, this);
253 g_HandleSys.InitAccessDefaults(NULL, &access);
255 /* Deny cloning to menus */
256 access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
257 m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
259 /* Also deny deletion to styles */
260 access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
261 m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
264 void MenuManager::OnSourceModAllShutdown()
266 g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
267 g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
269 while (!m_VoteHandlers.empty())
271 delete m_VoteHandlers.front();
272 m_VoteHandlers.pop();
276 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
278 if (type == m_MenuType)
280 IBaseMenu *menu = (IBaseMenu *)object;
281 menu->Destroy(false);
282 } else if (type == m_StyleType) {
287 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
289 if (m_MenuType == NO_HANDLE_TYPE)
294 return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
297 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
299 if (m_StyleType == NO_HANDLE_TYPE)
304 return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
307 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
311 sec.pIdentity = g_pCoreIdent;
314 return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
317 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
321 sec.pIdentity = g_pCoreIdent;
322 sec.pOwner = g_pCoreIdent;
324 return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
327 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
334 m_pDefaultStyle = style;
339 IMenuStyle *MenuManager::GetStyle(unsigned int index)
341 if (index >= GetStyleCount())
346 return m_Styles[index];
349 void MenuManager::AddStyle(IMenuStyle *style)
351 m_Styles.push_back(style);
354 unsigned int MenuManager::GetStyleCount()
356 return (unsigned int)m_Styles.size();
359 IMenuStyle *MenuManager::FindStyleByName(const char *name)
361 unsigned int count = GetStyleCount();
362 for (unsigned int i=0; i<count; i++)
364 IMenuStyle *ptr = GetStyle(i);
365 if (strcasecmp(ptr->GetStyleName(), name) == 0)
374 inline bool IsSlotItem(IMenuPanel *display,
377 if (!display->CanDrawItem(style))
381 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
385 if (style & ITEMDRAW_RAWLINE)
393 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
395 IBaseMenu *menu = md.menu;
404 unsigned int position;
408 /* Figure out how many items to draw */
409 IMenuStyle *style = menu->GetDrawStyle();
410 unsigned int pgn = menu->GetPagination();
411 unsigned int maxItems = style->GetMaxPageItems();
412 if (pgn != MENU_NO_PAGINATION)
417 unsigned int totalItems = menu->GetItemCount();
418 unsigned int startItem = 0;
420 /* For pagination, find the starting point. */
421 if (pgn != MENU_NO_PAGINATION)
423 if (order == ItemOrder_Ascending)
425 startItem = md.lastItem;
426 /* This shouldn't happen with well-coded menus.
427 * If the item is out of bounds, switch the order to
428 * Items_Descending and make us start from the top.
430 if (startItem >= totalItems)
432 startItem = totalItems - 1;
433 order = ItemOrder_Descending;
435 } else if (order == ItemOrder_Descending) {
436 startItem = md.firstItem;
437 /* This shouldn't happen with well-coded menus.
438 * If searching backwards doesn't give us enough room,
439 * start from the beginning and change to ascending.
441 if (startItem <= maxItems)
444 order = ItemOrder_Ascending;
449 /* Get our Display pointer and initialize some crap */
450 IMenuPanel *display = menu->CreatePanel();
451 IMenuHandler *mh = md.mh;
452 bool foundExtra = false;
453 unsigned int extraItem = 0;
456 * We keep searching until:
457 * 1) There are no more items
458 * 2) We reach one OVER the maximum number of slot items
459 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
461 unsigned int i = startItem;
462 unsigned int foundItems = 0;
465 ItemDrawInfo &dr = drawItems[foundItems].draw;
466 /* Is the item valid? */
467 if (menu->GetItemInfo(i, &dr) != NULL)
469 /* Ask the user to change the style, if necessary */
470 mh->OnMenuDrawItem(menu, client, i, dr.style);
471 /* Check if it's renderable */
472 if (IsSlotItem(display, dr.style))
474 /* If we've already found the max number of items,
475 * This means we should just cancel out and log our
478 if (foundItems >= maxItems)
484 drawItems[foundItems++].position = i;
487 /* If there's no pagination, stop once the menu is full. */
488 if (pgn == MENU_NO_PAGINATION)
490 /* If we've filled up, then stop */
491 if (foundItems >= maxItems)
496 /* If we're descending and this is the first item, stop */
497 if (order == ItemOrder_Descending)
505 /* If we're ascending and this is the last item, stop */
506 else if (order == ItemOrder_Ascending)
508 if (i >= totalItems - 1)
516 /* There were no items to draw! */
519 display->DeleteThis();
523 /* Check initial buttons */
524 bool displayPrev = false;
525 bool displayNext = false;
528 if (order == ItemOrder_Descending)
531 md.firstItem = extraItem;
532 } else if (order == ItemOrder_Ascending) {
534 md.lastItem = extraItem;
539 * If we're paginated, we have to find if there is another page.
540 * Sadly, the only way to do this is to try drawing more items!
542 if (pgn != MENU_NO_PAGINATION)
544 unsigned int lastItem = 0;
546 /* Find the last feasible item to search from. */
547 if (order == ItemOrder_Descending)
549 lastItem = drawItems[0].position;
550 if (lastItem >= totalItems - 1)
554 while (++lastItem < totalItems)
556 if (menu->GetItemInfo(lastItem, &dr) != NULL)
558 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
559 if (IsSlotItem(display, dr.style))
562 md.lastItem = lastItem;
567 } else if (order == ItemOrder_Ascending) {
568 lastItem = drawItems[0].position;
574 while (lastItem != 0)
576 if (menu->GetItemInfo(lastItem, &dr) != NULL)
578 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
579 if (IsSlotItem(display, dr.style))
582 md.firstItem = lastItem;
592 /* Draw the item according to the order */
593 menu_slots_t *slots = md.slots;
594 unsigned int position = 0; /* Keep track of the last position */
595 if (order == ItemOrder_Ascending)
597 for (unsigned int i=0; i<foundItems; i++)
599 ItemDrawInfo &dr = drawItems[i].draw;
600 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
602 position = display->DrawItem(dr);
606 slots[position].item = drawItems[i].position;
607 slots[position].type = ItemSel_Item;
610 } else if (order == ItemOrder_Descending) {
611 unsigned int i = foundItems;
612 /* NOTE: There will always be at least one item because
613 * of the check earlier.
617 ItemDrawInfo &dr = drawItems[i].draw;
618 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
620 position = display->DrawItem(dr);
622 if ((position = display->DrawItem(dr)) != 0)
624 slots[position].item = drawItems[i].position;
625 slots[position].type = ItemSel_Item;
630 /* Now, we need to check if we need to add anything extra */
631 if (pgn != MENU_NO_PAGINATION)
633 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
634 bool exitButton = menu->GetExitButton();
635 bool exitBackButton = menu->GetExitBackButton();
638 /* Calculate how many items we are allowed for control stuff */
639 unsigned int padding = style->GetMaxPageItems() - maxItems;
641 /* Add the number of available slots */
642 padding += (maxItems - foundItems);
644 /* Someday, if we are able to re-enable this, we will be very lucky men. */
646 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
649 /* Even if we don't draw an exit button, we invalidate the slot. */
653 /* Otherwise, we don't draw anything and leave the slot available */
658 /* Subtract two slots for the displayNext/displayPrev padding */
661 /* If we have an "Exit Back" button and the space to draw it, do so. */
668 exitBackButton = false;
673 * We allow next/prev to be undrawn if neither exists.
674 * Thus, we only need padding if one of them will be drawn,
675 * or the exit button will be drawn.
677 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
678 if (exitButton || (displayNext || displayPrev))
680 /* If there are no control options,
681 * Instead just pad with invisible slots.
683 if (!displayPrev && !displayPrev)
685 padItem.style = ITEMDRAW_NOTEXT;
687 /* Add spacers so we can pad to the end */
688 for (unsigned int i=0; i<padding; i++)
690 position = display->DrawItem(padItem);
691 slots[position].type = ItemSel_None;
695 /* Put a fake spacer before control stuff, if possible */
696 if ((displayPrev || displayNext) || exitButton)
698 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
699 display->DrawItem(draw);
702 ItemDrawInfo dr(text, 0);
704 * If we have one or the other, we need to have spacers for both.
706 if (displayPrev || displayNext)
709 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
710 if (displayPrev || canDrawDisabled)
714 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
715 dr.style = ITEMDRAW_CONTROL;
716 position = display->DrawItem(dr);
717 slots[position].type = ItemSel_ExitBack;
719 CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL);
720 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
721 position = display->DrawItem(dr);
722 slots[position].type = ItemSel_Back;
724 } else if (displayNext || exitButton) {
725 /* If we can't display this, and there is an exit button,
728 position = display->DrawItem(padCtrlItem);
729 slots[position].type = ItemSel_None;
733 if (displayNext || canDrawDisabled)
735 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
736 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
737 position = display->DrawItem(dr);
738 slots[position].type = ItemSel_Next;
739 } else if (exitButton) {
740 /* If we can't display this,
741 * but there is an "exit" button, we need to pad!
743 position = display->DrawItem(padCtrlItem);
744 slots[position].type = ItemSel_None;
747 /* Otherwise, bump to two slots! */
748 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
749 position = display->DrawItem(numBump);
750 slots[position].type = ItemSel_None;
751 position = display->DrawItem(numBump);
752 slots[position].type = ItemSel_None;
758 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
759 dr.style = ITEMDRAW_CONTROL;
760 position = display->DrawItem(dr);
761 slots[position].type = ItemSel_Exit;
765 /* Lastly, fill in any slots we could have missed */
766 for (unsigned int i = position + 1; i < 10; i++)
768 slots[i].type = ItemSel_None;
772 mh->OnMenuDisplay(menu, client, display);
773 display->DrawTitle(menu->GetDefaultTitle(), true);
778 IMenuStyle *MenuManager::GetDefaultStyle()
780 return m_pDefaultStyle;
783 IVoteMenuHandler *MenuManager::CreateVoteWrapper(IMenuHandler *mh)
785 VoteMenuHandler *vh = NULL;
787 if (m_VoteHandlers.empty())
789 vh = new VoteMenuHandler;
791 vh = m_VoteHandlers.front();
792 m_VoteHandlers.pop();
800 void MenuManager::ReleaseVoteWrapper(IVoteMenuHandler *mh)
807 m_VoteHandlers.push((VoteMenuHandler *)mh);
810 bool MenuManager::MenuSoundsEnabled()
812 return (sm_menu_sounds.GetInt() != 0);
815 ConfigResult MenuManager::OnSourceModConfigChanged(const char *key,
821 if (strcmp(key, "MenuItemSound") == 0)
823 m_SelectSound.assign(value);
824 return ConfigResult_Accept;
825 } else if (strcmp(key, "MenuExitBackSound") == 0) {
826 m_ExitBackSound.assign(value);
827 return ConfigResult_Accept;
830 return ConfigResult_Ignore;
833 const char *MenuManager::GetMenuSound(ItemSelection sel)
835 const char *sound = NULL;
844 if (m_SelectSound.size() > 0)
846 sound = m_SelectSound.c_str();
850 case ItemSel_ExitBack:
852 if (m_ExitBackSound.size() > 0)
854 sound = m_ExitBackSound.c_str();