3 * ================================================================
5 * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
6 * ================================================================
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License,
10 * version 3.0, as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to
21 * link the code of this program (as well as its derivative works) to
22 * "Half-Life 2," the "Source Engine," the "SourcePawn JIT," and any
23 * Game MODs that run on software by the Valve Corporation. You must
24 * obey the GNU General Public License in all respects for all other
25 * code used. Additionally, AlliedModders LLC grants this exception
26 * to all derivative works. AlliedModders LLC defines further
27 * exceptions, found in LICENSE.txt (as of this writing, version
28 * JULY-31-2007), or <http://www.sourcemod.net/license.php>.
36 #include "MenuManager.h"
37 #include "sm_stringutil.h"
38 #include "sourcemm_api.h"
39 #include "PlayerManager.h"
40 #include "MenuStyle_Valve.h"
42 #include "HandleSys.h"
43 #include "sourcemm_api.h"
47 ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds");
49 /*******************************
50 *******************************
51 ******** VOTE HANDLER *********
52 *******************************
53 *******************************/
55 unsigned int VoteMenuHandler::GetMenuAPIVersion2()
57 return m_pHandler->GetMenuAPIVersion2();
60 bool VoteMenuHandler::IsVoteInProgress()
62 return (m_pCurMenu != NULL);
65 void VoteMenuHandler::InitializeVoting(IBaseMenu *menu)
67 m_Items = menu->GetItemCount();
69 if (m_Votes.size() < (size_t)m_Items)
71 /* Only clear the items we need to... */
72 size_t size = m_Votes.size();
73 for (size_t i=0; i<size; i++)
77 m_Votes.resize(m_Items, 0);
79 for (unsigned int i=0; i<m_Items; i++)
87 m_pHandler->OnMenuStart(m_pCurMenu);
90 void VoteMenuHandler::StartVoting()
94 m_pHandler->OnMenuVoteStart(m_pCurMenu);
96 /* By now we know how many clients were set.
97 * If there are none, we should end IMMEDIATELY.
105 void VoteMenuHandler::DecrementPlayerCount()
107 assert(m_Clients > 0);
111 if (m_bStarted && m_Clients == 0)
117 void VoteMenuHandler::EndVoting()
121 /* If we were cancelled, don't bother tabulating anything.
122 * Reset just in case someone tries to redraw, which means
123 * we need to save our states.
125 IBaseMenu *menu = m_pCurMenu;
127 m_pHandler->OnMenuVoteCancel(menu);
128 m_pHandler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
132 unsigned int chosen = 0;
133 unsigned int highest = 0;
134 unsigned int dup_count = 0;
135 unsigned int total = m_Votes.size() ? m_Votes[0] : 0;
137 /* If we got zero votes, take a shortcut. */
140 /* Pick a random item and then jump far, far away. */
141 srand((unsigned int)(time(NULL)));
142 chosen = (unsigned int)rand() % m_Items;
146 /* We can't have more dups than this!
147 * This is the max number of players.
149 unsigned int dup_array[256];
151 for (size_t i=1; i<m_Items; i++)
153 if (m_Votes[i] > m_Votes[highest])
155 /* If we have a new highest count, mark it and trash the duplicate
156 * list by setting the total to 0.
160 } else if (m_Votes[i] == m_Votes[highest]) {
161 /* If they're equal, mark it in the duplicate list.
162 * We'll add in the original later.
164 dup_array[dup_count++] = i;
169 /* Check if we need to pick from the duplicate list */
172 /* Re-add the original to the list because it's not in there. */
173 dup_array[dup_count++] = highest;
175 /* Pick a random slot. */
176 srand((unsigned int)(time(NULL)));
177 unsigned int r = (unsigned int)rand() % dup_count;
180 chosen = dup_array[r];
186 m_pHandler->OnMenuVoteEnd(m_pCurMenu, chosen, m_Votes[highest], total);
187 m_pHandler->OnMenuEnd(m_pCurMenu, MenuEnd_VotingDone);
191 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
196 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
198 DecrementPlayerCount();
201 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
203 m_pHandler->OnMenuCancel(menu, client, reason);
206 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
208 m_pHandler->OnMenuDisplay(menu, client, display);
211 unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
213 return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
216 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
218 m_pHandler->OnMenuDrawItem(menu, client, item, style);
221 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
223 /* Check by our item count, NOT the vote array size */
230 m_pHandler->OnMenuSelect(menu, client, item);
233 void VoteMenuHandler::Reset(IMenuHandler *mh)
239 void VoteMenuHandler::InternalReset()
246 m_bCancelled = false;
249 void VoteMenuHandler::CancelVoting()
254 /*******************************
255 *******************************
256 ******** MENU MANAGER *********
257 *******************************
258 *******************************/
260 MenuManager::MenuManager()
262 m_Styles.push_back(&g_ValveMenuStyle);
263 SetDefaultStyle(&g_ValveMenuStyle);
266 void MenuManager::OnSourceModAllInitialized()
268 g_ShareSys.AddInterface(NULL, this);
271 g_HandleSys.InitAccessDefaults(NULL, &access);
273 /* Deny cloning to menus */
274 access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
275 m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
277 /* Also deny deletion to styles */
278 access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
279 m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
282 void MenuManager::OnSourceModAllShutdown()
284 g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
285 g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
287 while (!m_VoteHandlers.empty())
289 delete m_VoteHandlers.front();
290 m_VoteHandlers.pop();
294 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
296 if (type == m_MenuType)
298 IBaseMenu *menu = (IBaseMenu *)object;
299 menu->Destroy(false);
300 } else if (type == m_StyleType) {
305 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
307 if (m_MenuType == NO_HANDLE_TYPE)
312 return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
315 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
317 if (m_StyleType == NO_HANDLE_TYPE)
322 return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
325 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
329 sec.pIdentity = g_pCoreIdent;
332 return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
335 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
339 sec.pIdentity = g_pCoreIdent;
340 sec.pOwner = g_pCoreIdent;
342 return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
345 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
352 m_pDefaultStyle = style;
357 IMenuStyle *MenuManager::GetStyle(unsigned int index)
359 if (index >= GetStyleCount())
364 return m_Styles[index];
367 void MenuManager::AddStyle(IMenuStyle *style)
369 m_Styles.push_back(style);
372 unsigned int MenuManager::GetStyleCount()
374 return (unsigned int)m_Styles.size();
377 IMenuStyle *MenuManager::FindStyleByName(const char *name)
379 unsigned int count = GetStyleCount();
380 for (unsigned int i=0; i<count; i++)
382 IMenuStyle *ptr = GetStyle(i);
383 if (strcasecmp(ptr->GetStyleName(), name) == 0)
392 inline bool IsSlotItem(IMenuPanel *display,
395 if (!display->CanDrawItem(style))
399 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
403 if (style & ITEMDRAW_RAWLINE)
411 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
413 IBaseMenu *menu = md.menu;
422 unsigned int position;
426 /* Figure out how many items to draw */
427 IMenuStyle *style = menu->GetDrawStyle();
428 unsigned int pgn = menu->GetPagination();
429 unsigned int maxItems = style->GetMaxPageItems();
430 if (pgn != MENU_NO_PAGINATION)
435 unsigned int totalItems = menu->GetItemCount();
436 unsigned int startItem = 0;
438 /* For pagination, find the starting point. */
439 if (pgn != MENU_NO_PAGINATION)
441 if (order == ItemOrder_Ascending)
443 startItem = md.lastItem;
444 /* This shouldn't happen with well-coded menus.
445 * If the item is out of bounds, switch the order to
446 * Items_Descending and make us start from the top.
448 if (startItem >= totalItems)
450 startItem = totalItems - 1;
451 order = ItemOrder_Descending;
453 } else if (order == ItemOrder_Descending) {
454 startItem = md.firstItem;
455 /* This shouldn't happen with well-coded menus.
456 * If searching backwards doesn't give us enough room,
457 * start from the beginning and change to ascending.
459 if (startItem <= maxItems)
462 order = ItemOrder_Ascending;
467 /* Get our Display pointer and initialize some crap */
468 IMenuPanel *display = menu->CreatePanel();
469 IMenuHandler *mh = md.mh;
470 bool foundExtra = false;
471 unsigned int extraItem = 0;
474 * We keep searching until:
475 * 1) There are no more items
476 * 2) We reach one OVER the maximum number of slot items
477 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
479 unsigned int i = startItem;
480 unsigned int foundItems = 0;
483 ItemDrawInfo &dr = drawItems[foundItems].draw;
484 /* Is the item valid? */
485 if (menu->GetItemInfo(i, &dr) != NULL)
487 /* Ask the user to change the style, if necessary */
488 mh->OnMenuDrawItem(menu, client, i, dr.style);
489 /* Check if it's renderable */
490 if (IsSlotItem(display, dr.style))
492 /* If we've already found the max number of items,
493 * This means we should just cancel out and log our
496 if (foundItems >= maxItems)
502 drawItems[foundItems++].position = i;
505 /* If there's no pagination, stop once the menu is full. */
506 if (pgn == MENU_NO_PAGINATION)
508 /* If we've filled up, then stop */
509 if (foundItems >= maxItems)
514 /* If we're descending and this is the first item, stop */
515 if (order == ItemOrder_Descending)
523 /* If we're ascending and this is the last item, stop */
524 else if (order == ItemOrder_Ascending)
526 if (i >= totalItems - 1)
534 /* There were no items to draw! */
537 display->DeleteThis();
541 /* Check initial buttons */
542 bool displayPrev = false;
543 bool displayNext = false;
546 if (order == ItemOrder_Descending)
549 md.firstItem = extraItem;
550 } else if (order == ItemOrder_Ascending) {
552 md.lastItem = extraItem;
557 * If we're paginated, we have to find if there is another page.
558 * Sadly, the only way to do this is to try drawing more items!
560 if (pgn != MENU_NO_PAGINATION)
562 unsigned int lastItem = 0;
564 /* Find the last feasible item to search from. */
565 if (order == ItemOrder_Descending)
567 lastItem = drawItems[0].position;
568 if (lastItem >= totalItems - 1)
572 while (++lastItem < totalItems)
574 if (menu->GetItemInfo(lastItem, &dr) != NULL)
576 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
577 if (IsSlotItem(display, dr.style))
580 md.lastItem = lastItem;
585 } else if (order == ItemOrder_Ascending) {
586 lastItem = drawItems[0].position;
592 while (lastItem != 0)
594 if (menu->GetItemInfo(lastItem, &dr) != NULL)
596 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
597 if (IsSlotItem(display, dr.style))
600 md.firstItem = lastItem;
610 /* Draw the item according to the order */
611 menu_slots_t *slots = md.slots;
612 unsigned int position = 0; /* Keep track of the last position */
613 if (order == ItemOrder_Ascending)
615 for (unsigned int i=0; i<foundItems; i++)
617 ItemDrawInfo &dr = drawItems[i].draw;
618 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
620 position = display->DrawItem(dr);
624 slots[position].item = drawItems[i].position;
625 slots[position].type = ItemSel_Item;
628 } else if (order == ItemOrder_Descending) {
629 unsigned int i = foundItems;
630 /* NOTE: There will always be at least one item because
631 * of the check earlier.
635 ItemDrawInfo &dr = drawItems[i].draw;
636 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
638 position = display->DrawItem(dr);
642 slots[position].item = drawItems[i].position;
643 slots[position].type = ItemSel_Item;
648 /* Now, we need to check if we need to add anything extra */
649 if (pgn != MENU_NO_PAGINATION)
651 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
652 bool exitButton = menu->GetExitButton();
653 bool exitBackButton = menu->GetExitBackButton();
656 /* Calculate how many items we are allowed for control stuff */
657 unsigned int padding = style->GetMaxPageItems() - maxItems;
659 /* Add the number of available slots */
660 padding += (maxItems - foundItems);
662 /* Someday, if we are able to re-enable this, we will be very lucky men. */
664 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
667 /* Even if we don't draw an exit button, we invalidate the slot. */
671 /* Otherwise, we don't draw anything and leave the slot available */
676 /* Subtract two slots for the displayNext/displayPrev padding */
679 /* If we have an "Exit Back" button and the space to draw it, do so. */
686 exitBackButton = false;
691 * We allow next/prev to be undrawn if neither exists.
692 * Thus, we only need padding if one of them will be drawn,
693 * or the exit button will be drawn.
695 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
696 if (exitButton || (displayNext || displayPrev))
698 /* If there are no control options,
699 * Instead just pad with invisible slots.
701 if (!displayPrev && !displayPrev)
703 padItem.style = ITEMDRAW_NOTEXT;
705 /* Add spacers so we can pad to the end */
706 for (unsigned int i=0; i<padding; i++)
708 position = display->DrawItem(padItem);
709 slots[position].type = ItemSel_None;
713 /* Put a fake spacer before control stuff, if possible */
714 if ((displayPrev || displayNext) || exitButton)
716 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
717 display->DrawItem(draw);
720 ItemDrawInfo dr(text, 0);
722 * If we have one or the other, we need to have spacers for both.
724 if (displayPrev || displayNext)
727 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
728 if (displayPrev || canDrawDisabled)
732 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
733 dr.style = ITEMDRAW_CONTROL;
734 position = display->DrawItem(dr);
735 slots[position].type = ItemSel_ExitBack;
737 CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL);
738 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
739 position = display->DrawItem(dr);
740 slots[position].type = ItemSel_Back;
742 } else if (displayNext || exitButton) {
743 /* If we can't display this, and there is an exit button,
746 position = display->DrawItem(padCtrlItem);
747 slots[position].type = ItemSel_None;
751 if (displayNext || canDrawDisabled)
753 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
754 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
755 position = display->DrawItem(dr);
756 slots[position].type = ItemSel_Next;
757 } else if (exitButton) {
758 /* If we can't display this,
759 * but there is an "exit" button, we need to pad!
761 position = display->DrawItem(padCtrlItem);
762 slots[position].type = ItemSel_None;
765 /* Otherwise, bump to two slots! */
766 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
767 position = display->DrawItem(numBump);
768 slots[position].type = ItemSel_None;
769 position = display->DrawItem(numBump);
770 slots[position].type = ItemSel_None;
776 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
777 dr.style = ITEMDRAW_CONTROL;
778 position = display->DrawItem(dr);
779 slots[position].type = ItemSel_Exit;
783 /* Lastly, fill in any slots we could have missed */
784 for (unsigned int i = position + 1; i < 10; i++)
786 slots[i].type = ItemSel_None;
790 mh->OnMenuDisplay(menu, client, display);
791 display->DrawTitle(menu->GetDefaultTitle(), true);
796 IMenuStyle *MenuManager::GetDefaultStyle()
798 return m_pDefaultStyle;
801 IVoteMenuHandler *MenuManager::CreateVoteWrapper(IMenuHandler *mh)
803 VoteMenuHandler *vh = NULL;
805 if (m_VoteHandlers.empty())
807 vh = new VoteMenuHandler;
809 vh = m_VoteHandlers.front();
810 m_VoteHandlers.pop();
818 void MenuManager::ReleaseVoteWrapper(IVoteMenuHandler *mh)
825 m_VoteHandlers.push((VoteMenuHandler *)mh);
828 bool MenuManager::MenuSoundsEnabled()
830 return (sm_menu_sounds.GetInt() != 0);
833 ConfigResult MenuManager::OnSourceModConfigChanged(const char *key,
839 if (strcmp(key, "MenuItemSound") == 0)
841 m_SelectSound.assign(value);
842 return ConfigResult_Accept;
843 } else if (strcmp(key, "MenuExitBackSound") == 0) {
844 m_ExitBackSound.assign(value);
845 return ConfigResult_Accept;
846 } else if (strcmp(key, "MenuExitSound") == 0) {
847 m_ExitSound.assign(value);
848 return ConfigResult_Accept;
851 return ConfigResult_Ignore;
854 const char *MenuManager::GetMenuSound(ItemSelection sel)
856 const char *sound = NULL;
864 if (m_SelectSound.size() > 0)
866 sound = m_SelectSound.c_str();
870 case ItemSel_ExitBack:
872 if (m_ExitBackSound.size() > 0)
874 sound = m_ExitBackSound.c_str();
880 if (m_ExitSound.size() > 0)
882 sound= m_ExitSound.c_str();