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"
28 /*******************************
29 *******************************
30 ******** VOTE HANDLER *********
31 *******************************
32 *******************************/
34 unsigned int VoteMenuHandler::GetMenuAPIVersion2()
36 return m_pHandler->GetMenuAPIVersion2();
39 bool VoteMenuHandler::IsVoteInProgress()
41 return (m_pCurMenu != NULL);
44 void VoteMenuHandler::InitializeVoting(IBaseMenu *menu)
46 m_Items = menu->GetItemCount();
48 if (m_Votes.size() < (size_t)m_Items)
50 /* Only clear the items we need to... */
51 size_t size = m_Votes.size();
52 for (size_t i=0; i<size; i++)
56 m_Votes.resize(m_Items, 0);
58 for (unsigned int i=0; i<m_Items; i++)
66 m_pHandler->OnMenuStart(m_pCurMenu);
69 void VoteMenuHandler::StartVoting()
73 m_pHandler->OnMenuVoteStart(m_pCurMenu);
75 /* By now we know how many clients were set.
76 * If there are none, we should end IMMEDIATELY.
84 void VoteMenuHandler::DecrementPlayerCount()
86 assert(m_Clients > 0);
90 if (m_bStarted && m_Clients == 0)
96 void VoteMenuHandler::EndVoting()
100 /* If we were cancelled, don't bother tabulating anything.
101 * Reset just in case someone tries to redraw, which means
102 * we need to save our states.
104 IBaseMenu *menu = m_pCurMenu;
106 m_pHandler->OnMenuVoteCancel(menu);
107 m_pHandler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
111 unsigned int chosen = 0;
112 unsigned int highest = 0;
113 unsigned int dup_count = 0;
114 unsigned int total = m_Votes.size() ? m_Votes[0] : 0;
116 /* If we got zero votes, take a shortcut. */
119 /* Pick a random item and then jump far, far away. */
120 srand((unsigned int)(time(NULL)));
121 chosen = (unsigned int)rand() % m_Items;
125 /* We can't have more dups than this!
126 * This is the max number of players.
128 unsigned int dup_array[256];
130 for (size_t i=1; i<m_Items; i++)
132 if (m_Votes[i] > m_Votes[highest])
134 /* If we have a new highest count, mark it and trash the duplicate
135 * list by setting the total to 0.
139 } else if (m_Votes[i] == m_Votes[highest]) {
140 /* If they're equal, mark it in the duplicate list.
141 * We'll add in the original later.
143 dup_array[dup_count++] = i;
148 /* Check if we need to pick from the duplicate list */
151 /* Re-add the original to the list because it's not in there. */
152 dup_array[dup_count++] = highest;
154 /* Pick a random slot. */
155 srand((unsigned int)(time(NULL)));
156 unsigned int r = (unsigned int)rand() % dup_count;
159 chosen = dup_array[r];
165 m_pHandler->OnMenuVoteEnd(m_pCurMenu, chosen, m_Votes[highest], total);
166 m_pHandler->OnMenuEnd(m_pCurMenu, MenuEnd_VotingDone);
170 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
175 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
177 DecrementPlayerCount();
180 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
182 m_pHandler->OnMenuCancel(menu, client, reason);
185 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
187 m_pHandler->OnMenuDisplay(menu, client, display);
190 void VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display)
192 m_pHandler->OnMenuDisplayItem(menu, client, item, display);
195 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
197 m_pHandler->OnMenuDrawItem(menu, client, item, style);
200 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
202 /* Check by our item count, NOT the vote array size */
209 m_pHandler->OnMenuSelect(menu, client, item);
212 void VoteMenuHandler::Reset(IMenuHandler *mh)
218 void VoteMenuHandler::InternalReset()
225 m_bCancelled = false;
228 void VoteMenuHandler::CancelVoting()
233 /*******************************
234 *******************************
235 ******** MENU MANAGER *********
236 *******************************
237 *******************************/
239 MenuManager::MenuManager()
241 m_Styles.push_back(&g_ValveMenuStyle);
242 SetDefaultStyle(&g_ValveMenuStyle);
245 void MenuManager::OnSourceModAllInitialized()
247 g_ShareSys.AddInterface(NULL, this);
250 g_HandleSys.InitAccessDefaults(NULL, &access);
252 /* Deny cloning to menus */
253 access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
254 m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
256 /* Also deny deletion to styles */
257 access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
258 m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
261 void MenuManager::OnSourceModAllShutdown()
263 g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
264 g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
266 while (!m_VoteHandlers.empty())
268 delete m_VoteHandlers.front();
269 m_VoteHandlers.pop();
273 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
275 if (type == m_MenuType)
277 IBaseMenu *menu = (IBaseMenu *)object;
278 menu->Destroy(false);
279 } else if (type == m_StyleType) {
284 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
286 if (m_MenuType == NO_HANDLE_TYPE)
291 return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
294 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
296 if (m_StyleType == NO_HANDLE_TYPE)
301 return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
304 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
308 sec.pIdentity = g_pCoreIdent;
311 return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
314 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
318 sec.pIdentity = g_pCoreIdent;
319 sec.pOwner = g_pCoreIdent;
321 return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
324 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
331 m_pDefaultStyle = style;
336 IMenuStyle *MenuManager::GetStyle(unsigned int index)
338 if (index >= GetStyleCount())
343 return m_Styles[index];
346 void MenuManager::AddStyle(IMenuStyle *style)
348 m_Styles.push_back(style);
351 unsigned int MenuManager::GetStyleCount()
353 return (unsigned int)m_Styles.size();
356 IMenuStyle *MenuManager::FindStyleByName(const char *name)
358 unsigned int count = GetStyleCount();
359 for (unsigned int i=0; i<count; i++)
361 IMenuStyle *ptr = GetStyle(i);
362 if (strcasecmp(ptr->GetStyleName(), name) == 0)
371 inline bool IsSlotItem(IMenuPanel *display,
374 if (!display->CanDrawItem(style))
378 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
382 if (style & ITEMDRAW_RAWLINE)
390 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
392 IBaseMenu *menu = md.menu;
401 unsigned int position;
405 /* Figure out how many items to draw */
406 IMenuStyle *style = menu->GetDrawStyle();
407 unsigned int pgn = menu->GetPagination();
408 unsigned int maxItems = style->GetMaxPageItems();
409 if (pgn != MENU_NO_PAGINATION)
414 unsigned int totalItems = menu->GetItemCount();
415 unsigned int startItem = 0;
417 /* For pagination, find the starting point. */
418 if (pgn != MENU_NO_PAGINATION)
420 if (order == ItemOrder_Ascending)
422 startItem = md.lastItem;
423 /* This shouldn't happen with well-coded menus.
424 * If the item is out of bounds, switch the order to
425 * Items_Descending and make us start from the top.
427 if (startItem >= totalItems)
429 startItem = totalItems - 1;
430 order = ItemOrder_Descending;
432 } else if (order == ItemOrder_Descending) {
433 startItem = md.firstItem;
434 /* This shouldn't happen with well-coded menus.
435 * If searching backwards doesn't give us enough room,
436 * start from the beginning and change to ascending.
438 if (startItem <= maxItems)
441 order = ItemOrder_Ascending;
446 /* Get our Display pointer and initialize some crap */
447 IMenuPanel *display = menu->CreatePanel();
448 IMenuHandler *mh = md.mh;
449 bool foundExtra = false;
450 unsigned int extraItem = 0;
453 * We keep searching until:
454 * 1) There are no more items
455 * 2) We reach one OVER the maximum number of slot items
456 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
458 unsigned int i = startItem;
459 unsigned int foundItems = 0;
462 ItemDrawInfo &dr = drawItems[foundItems].draw;
463 /* Is the item valid? */
464 if (menu->GetItemInfo(i, &dr) != NULL)
466 /* Ask the user to change the style, if necessary */
467 mh->OnMenuDrawItem(menu, client, i, dr.style);
468 /* Check if it's renderable */
469 if (IsSlotItem(display, dr.style))
471 /* If we've already found the max number of items,
472 * This means we should just cancel out and log our
475 if (foundItems >= maxItems)
481 drawItems[foundItems++].position = i;
484 /* If there's no pagination, stop once the menu is full. */
485 if (pgn == MENU_NO_PAGINATION)
487 /* If we've filled up, then stop */
488 if (foundItems >= maxItems)
493 /* If we're descending and this is the first item, stop */
494 if (order == ItemOrder_Descending)
502 /* If we're ascending and this is the last item, stop */
503 else if (order == ItemOrder_Ascending)
505 if (i >= totalItems - 1)
513 /* There were no items to draw! */
516 display->DeleteThis();
520 /* Check initial buttons */
521 bool displayPrev = false;
522 bool displayNext = false;
525 if (order == ItemOrder_Descending)
528 md.firstItem = extraItem;
529 } else if (order == ItemOrder_Ascending) {
531 md.lastItem = extraItem;
536 * If we're paginated, we have to find if there is another page.
537 * Sadly, the only way to do this is to try drawing more items!
539 if (pgn != MENU_NO_PAGINATION)
541 unsigned int lastItem = 0;
543 /* Find the last feasible item to search from. */
544 if (order == ItemOrder_Descending)
546 lastItem = drawItems[0].position;
547 if (lastItem >= totalItems - 1)
551 while (++lastItem < totalItems)
553 if (menu->GetItemInfo(lastItem, &dr) != NULL)
555 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
556 if (IsSlotItem(display, dr.style))
559 md.lastItem = lastItem;
564 } else if (order == ItemOrder_Ascending) {
565 lastItem = drawItems[0].position;
571 while (lastItem != 0)
573 if (menu->GetItemInfo(lastItem, &dr) != NULL)
575 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
576 if (IsSlotItem(display, dr.style))
579 md.firstItem = lastItem;
589 /* Draw the item according to the order */
590 menu_slots_t *slots = md.slots;
591 unsigned int position = 0; /* Keep track of the last position */
592 if (order == ItemOrder_Ascending)
594 for (unsigned int i=0; i<foundItems; i++)
596 ItemDrawInfo &dr = drawItems[i].draw;
597 menu->GetItemInfo(drawItems[i].position, &dr);
598 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
599 if ((position = display->DrawItem(dr)) != 0)
601 slots[position].item = drawItems[i].position;
602 slots[position].type = ItemSel_Item;
605 } else if (order == ItemOrder_Descending) {
606 unsigned int i = foundItems;
607 /* NOTE: There will always be at least one item because
608 * of the check earlier.
612 ItemDrawInfo &dr = drawItems[i].draw;
613 menu->GetItemInfo(drawItems[i].position, &dr);
614 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
615 if ((position = display->DrawItem(dr)) != 0)
617 slots[position].item = drawItems[i].position;
618 slots[position].type = ItemSel_Item;
623 /* Now, we need to check if we need to add anything extra */
624 if (pgn != MENU_NO_PAGINATION)
626 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
627 bool exitButton = menu->GetExitButton();
628 bool exitBackButton = menu->GetExitBackButton();
631 /* Calculate how many items we are allowed for control stuff */
632 unsigned int padding = style->GetMaxPageItems() - maxItems;
634 /* Add the number of available slots */
635 padding += (maxItems - foundItems);
637 /* Someday, if we are able to re-enable this, we will be very lucky men. */
639 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
642 /* Even if we don't draw an exit button, we invalidate the slot. */
646 /* Otherwise, we don't draw anything and leave the slot available */
651 /* Subtract two slots for the displayNext/displayPrev padding */
654 /* If we have an "Exit Back" button and the space to draw it, do so. */
661 exitBackButton = false;
666 * We allow next/prev to be undrawn if neither exists.
667 * Thus, we only need padding if one of them will be drawn,
668 * or the exit button will be drawn.
670 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
671 if (exitButton || (displayNext || displayPrev))
673 /* If there are no control options,
674 * Instead just pad with invisible slots.
676 if (!displayPrev && !displayPrev)
678 padItem.style = ITEMDRAW_NOTEXT;
680 /* Add spacers so we can pad to the end */
681 for (unsigned int i=0; i<padding; i++)
683 position = display->DrawItem(padItem);
684 slots[position].type = ItemSel_None;
688 /* Put a fake spacer before control stuff, if possible */
689 if ((displayPrev || displayNext) || exitButton)
691 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
692 display->DrawItem(draw);
695 ItemDrawInfo dr(text, 0);
697 * If we have one or the other, we need to have spacers for both.
699 if (displayPrev || displayNext)
702 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
703 if (displayPrev || canDrawDisabled)
707 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
708 dr.style = ITEMDRAW_CONTROL;
709 position = display->DrawItem(dr);
710 slots[position].type = ItemSel_ExitBack;
712 CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL);
713 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
714 position = display->DrawItem(dr);
715 slots[position].type = ItemSel_Back;
717 } else if (displayNext || exitButton) {
718 /* If we can't display this, and there is an exit button,
721 position = display->DrawItem(padCtrlItem);
722 slots[position].type = ItemSel_None;
726 if (displayNext || canDrawDisabled)
728 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
729 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
730 position = display->DrawItem(dr);
731 slots[position].type = ItemSel_Next;
732 } else if (exitButton) {
733 /* If we can't display this,
734 * but there is an "exit" button, we need to pad!
736 position = display->DrawItem(padCtrlItem);
737 slots[position].type = ItemSel_None;
740 /* Otherwise, bump to two slots! */
741 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
742 position = display->DrawItem(numBump);
743 slots[position].type = ItemSel_None;
744 position = display->DrawItem(numBump);
745 slots[position].type = ItemSel_None;
751 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
752 dr.style = ITEMDRAW_CONTROL;
753 position = display->DrawItem(dr);
754 slots[position].type = ItemSel_Exit;
758 /* Lastly, fill in any slots we could have missed */
759 for (unsigned int i = position + 1; i < 10; i++)
761 slots[i].type = ItemSel_None;
765 mh->OnMenuDisplay(menu, client, display);
766 display->DrawTitle(menu->GetDefaultTitle(), true);
771 IMenuStyle *MenuManager::GetDefaultStyle()
773 return m_pDefaultStyle;
776 IVoteMenuHandler *MenuManager::CreateVoteWrapper(IMenuHandler *mh)
778 VoteMenuHandler *vh = NULL;
780 if (m_VoteHandlers.empty())
782 vh = new VoteMenuHandler;
784 vh = m_VoteHandlers.front();
785 m_VoteHandlers.pop();
793 void MenuManager::ReleaseVoteWrapper(IVoteMenuHandler *mh)
800 m_VoteHandlers.push((VoteMenuHandler *)mh);