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 **** BROADCAST HANDLING WRAPPERS ****
31 *************************************
32 *************************************/
34 BroadcastHandler::BroadcastHandler(IMenuHandler *handler) : m_pHandler(handler), numClients(0)
38 unsigned int BroadcastHandler::GetMenuAPIVersion2()
40 return m_pHandler->GetMenuAPIVersion2();
43 void BroadcastHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
45 m_pHandler->OnMenuCancel(menu, client, reason);
48 void BroadcastHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
51 m_pHandler->OnMenuDisplay(menu, client, display);
54 void BroadcastHandler::OnBroadcastEnd(IBaseMenu *menu)
56 g_Menus.FreeBroadcastHandler(this);
59 void BroadcastHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
61 m_pHandler->OnMenuSelect(menu, client, item);
64 void BroadcastHandler::OnMenuEnd(IBaseMenu *menu)
66 assert(numClients > 0);
68 /* Only fire if all clients have gotten a menu end */
69 if (--numClients == 0)
71 IMenuHandler *pHandler = m_pHandler;
73 pHandler->OnMenuEnd(menu);
77 VoteHandler::VoteHandler(IMenuVoteHandler *handler)
78 : BroadcastHandler(handler), m_pVoteHandler(handler)
82 void VoteHandler::Initialize(IBaseMenu *menu)
84 unsigned int numItems = menu->GetItemCount();
86 if (m_counts.size() >= numItems)
88 for (size_t i=0; i<numItems; i++)
93 for (size_t i=0; i<m_counts.size(); i++)
97 m_counts.resize(numItems, 0);
101 void VoteHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
108 BroadcastHandler::OnMenuSelect(menu, client, item);
111 void VoteHandler::OnBroadcastEnd(IBaseMenu *menu)
116 for (size_t i=1; i<numItems; i++)
118 if (m_counts[i] > m_counts[highest])
122 } else if (m_counts[i] == m_counts[highest]) {
129 m_ties.push_back(highest);
130 srand(static_cast<unsigned int>(time(NULL)));
131 highest = m_ties[rand() % m_ties.size()];
134 m_pVoteHandler->OnMenuVoteEnd(menu, highest);
136 g_Menus.FreeVoteHandler(this);
139 /*******************************
140 *******************************
141 ******** MENU MANAGER *********
142 *******************************
143 *******************************/
145 MenuManager::MenuManager()
147 m_Styles.push_back(&g_ValveMenuStyle);
148 SetDefaultStyle(&g_ValveMenuStyle);
151 void MenuManager::OnSourceModAllInitialized()
153 g_ShareSys.AddInterface(NULL, this);
156 g_HandleSys.InitAccessDefaults(NULL, &access);
158 /* Deny cloning to menus */
159 access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
160 m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
162 /* Also deny deletion to styles */
163 access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
164 m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
167 void MenuManager::OnSourceModAllShutdown()
169 g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
170 g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
172 while (!m_BroadcastHandlers.empty())
174 delete m_BroadcastHandlers.front();
175 m_BroadcastHandlers.pop();
178 while (!m_VoteHandlers.empty())
180 delete m_VoteHandlers.front();
181 m_VoteHandlers.pop();
185 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
187 if (type == m_MenuType)
189 IBaseMenu *menu = (IBaseMenu *)object;
190 menu->Destroy(false);
191 } else if (type == m_StyleType) {
196 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
198 if (m_MenuType == NO_HANDLE_TYPE)
203 return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
206 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
208 if (m_StyleType == NO_HANDLE_TYPE)
213 return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
216 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
220 sec.pIdentity = g_pCoreIdent;
223 return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
226 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
230 sec.pIdentity = g_pCoreIdent;
231 sec.pOwner = g_pCoreIdent;
233 return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
236 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
243 m_pDefaultStyle = style;
248 IMenuStyle *MenuManager::GetStyle(unsigned int index)
250 if (index >= GetStyleCount())
255 return m_Styles[index];
258 void MenuManager::AddStyle(IMenuStyle *style)
260 m_Styles.push_back(style);
263 unsigned int MenuManager::GetStyleCount()
265 return (unsigned int)m_Styles.size();
268 IMenuStyle *MenuManager::FindStyleByName(const char *name)
270 unsigned int count = GetStyleCount();
271 for (unsigned int i=0; i<count; i++)
273 IMenuStyle *ptr = GetStyle(i);
274 if (strcasecmp(ptr->GetStyleName(), name) == 0)
283 inline bool IsSlotItem(IMenuPanel *display,
286 if (!display->CanDrawItem(style))
290 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
294 if (style & ITEMDRAW_RAWLINE)
302 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
304 IBaseMenu *menu = md.menu;
313 unsigned int position;
317 /* Figure out how many items to draw */
318 IMenuStyle *style = menu->GetDrawStyle();
319 unsigned int pgn = menu->GetPagination();
320 unsigned int maxItems = style->GetMaxPageItems();
321 if (pgn != MENU_NO_PAGINATION)
326 unsigned int totalItems = menu->GetItemCount();
327 unsigned int startItem = 0;
329 /* For pagination, find the starting point. */
330 if (pgn != MENU_NO_PAGINATION)
332 if (order == ItemOrder_Ascending)
334 startItem = md.lastItem;
335 /* This shouldn't happen with well-coded menus.
336 * If the item is out of bounds, switch the order to
337 * Items_Descending and make us start from the top.
339 if (startItem >= totalItems)
341 startItem = totalItems - 1;
342 order = ItemOrder_Descending;
344 } else if (order == ItemOrder_Descending) {
345 startItem = md.firstItem;
346 /* This shouldn't happen with well-coded menus.
347 * If searching backwards doesn't give us enough room,
348 * start from the beginning and change to ascending.
350 if (startItem <= maxItems)
353 order = ItemOrder_Ascending;
358 /* Get our Display pointer and initialize some crap */
359 IMenuPanel *display = menu->CreatePanel();
360 IMenuHandler *mh = md.mh;
361 bool foundExtra = false;
362 unsigned int extraItem = 0;
365 * We keep searching until:
366 * 1) There are no more items
367 * 2) We reach one OVER the maximum number of slot items
368 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
370 unsigned int i = startItem;
371 unsigned int foundItems = 0;
374 ItemDrawInfo &dr = drawItems[foundItems].draw;
375 /* Is the item valid? */
376 if (menu->GetItemInfo(i, &dr) != NULL)
378 /* Ask the user to change the style, if necessary */
379 mh->OnMenuDrawItem(menu, client, i, dr.style);
380 /* Check if it's renderable */
381 if (IsSlotItem(display, dr.style))
383 /* If we've already found the max number of items,
384 * This means we should just cancel out and log our
387 if (foundItems >= maxItems)
393 drawItems[foundItems++].position = i;
396 /* If there's no pagination, stop once the menu is full. */
397 if (pgn == MENU_NO_PAGINATION)
399 /* If we've filled up, then stop */
400 if (foundItems >= maxItems)
405 /* If we're descending and this is the first item, stop */
406 if (order == ItemOrder_Descending)
414 /* If we're ascending and this is the last item, stop */
415 else if (order == ItemOrder_Ascending)
417 if (i >= totalItems - 1)
425 /* There were no items to draw! */
428 display->DeleteThis();
432 /* Check initial buttons */
433 bool displayPrev = false;
434 bool displayNext = false;
437 if (order == ItemOrder_Descending)
440 md.firstItem = extraItem;
441 } else if (order == ItemOrder_Ascending) {
443 md.lastItem = extraItem;
448 * If we're paginated, we have to find if there is another page.
449 * Sadly, the only way to do this is to try drawing more items!
451 if (pgn != MENU_NO_PAGINATION)
453 unsigned int lastItem = 0;
455 /* Find the last feasible item to search from. */
456 if (order == ItemOrder_Descending)
458 lastItem = drawItems[0].position;
459 if (lastItem >= totalItems - 1)
463 while (++lastItem < totalItems)
465 if (menu->GetItemInfo(lastItem, &dr) != NULL)
467 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
468 if (IsSlotItem(display, dr.style))
471 md.lastItem = lastItem;
476 } else if (order == ItemOrder_Ascending) {
477 lastItem = drawItems[0].position;
483 while (lastItem-- != 0)
485 if (menu->GetItemInfo(lastItem, &dr) != NULL)
487 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
488 if (IsSlotItem(display, dr.style))
491 md.firstItem = lastItem;
500 /* Draw the item according to the order */
501 menu_slots_t *slots = md.slots;
502 unsigned int position = 0; /* Keep track of the last position */
503 if (order == ItemOrder_Ascending)
505 for (unsigned int i=0; i<foundItems; i++)
507 ItemDrawInfo &dr = drawItems[i].draw;
508 menu->GetItemInfo(drawItems[i].position, &dr);
509 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
510 if ((position = display->DrawItem(dr)) != 0)
512 slots[position].item = drawItems[i].position;
513 slots[position].type = ItemSel_Item;
516 } else if (order == ItemOrder_Descending) {
517 unsigned int i = foundItems;
518 /* NOTE: There will always be at least one item because
519 * of the check earlier.
523 ItemDrawInfo &dr = drawItems[i].draw;
524 menu->GetItemInfo(drawItems[i].position, &dr);
525 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
526 if ((position = display->DrawItem(dr)) != 0)
528 slots[position].item = drawItems[i].position;
529 slots[position].type = ItemSel_Item;
534 /* Now, we need to check if we need to add anything extra */
535 if (pgn != MENU_NO_PAGINATION)
537 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
538 bool exitButton = menu->GetExitButton();
541 /* Calculate how many items we are allowed for control stuff */
542 unsigned int padding = style->GetMaxPageItems() - maxItems;
544 /* Add the number of available slots */
545 padding += (maxItems - foundItems);
547 /* Someday, if we are able to re-enable this, we will be very lucky men. */
549 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
552 /* Even if we don't draw an exit button, we invalidate the slot. */
556 /* Otherwise, we don't draw anything and leave the slot available */
561 /* Subtract two slots for the displayNext/displayPrev padding */
565 * We allow next/prev to be undrawn if neither exists.
566 * Thus, we only need padding if one of them will be drawn,
567 * or the exit button will be drawn.
569 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
570 if (exitButton || (displayNext || displayPrev))
572 /* If there are no control options,
573 * Instead just pad with invisible slots.
575 if (!displayPrev && !displayPrev)
577 padItem.style = ITEMDRAW_NOTEXT;
579 /* Add spacers so we can pad to the end */
580 for (unsigned int i=0; i<padding; i++)
582 position = display->DrawItem(padItem);
583 slots[position].type = ItemSel_None;
587 /* Put a fake spacer before control stuff, if possible */
589 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
590 display->DrawItem(draw);
593 ItemDrawInfo dr(text, 0);
595 * If we have one or the other, we need to have spacers for both.
597 if (displayPrev || displayNext)
600 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
601 if (displayPrev || canDrawDisabled)
603 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
604 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
605 position = display->DrawItem(dr);
606 slots[position].type = ItemSel_Back;
607 } else if (displayNext || exitButton) {
608 /* If we can't display this, and there is an exit button,
611 position = display->DrawItem(padCtrlItem);
612 slots[position].type = ItemSel_None;
616 if (displayNext || canDrawDisabled)
618 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
619 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
620 position = display->DrawItem(dr);
621 slots[position].type = ItemSel_Next;
622 } else if (exitButton) {
623 /* If we can't display this,
624 * but there is an "exit" button, we need to pad!
626 position = display->DrawItem(padCtrlItem);
627 slots[position].type = ItemSel_None;
630 /* Otherwise, bump to two slots! */
631 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
632 position = display->DrawItem(numBump);
633 slots[position].type = ItemSel_None;
634 position = display->DrawItem(numBump);
635 slots[position].type = ItemSel_None;
641 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
642 dr.style = ITEMDRAW_CONTROL;
643 position = display->DrawItem(dr);
644 slots[position].type = ItemSel_Exit;
648 /* Lastly, fill in any slots we could have missed */
649 for (unsigned int i = position + 1; i < 10; i++)
651 slots[i].type = ItemSel_None;
655 mh->OnMenuDisplay(menu, client, display);
656 display->DrawTitle(menu->GetDefaultTitle(), true);
662 unsigned int MenuManager::BroadcastMenu(IBaseMenu *menu,
663 IMenuHandler *handler,
665 unsigned int numClients,
668 BroadcastHandler *bh;
670 if (m_BroadcastHandlers.empty())
672 bh = new BroadcastHandler(handler);
674 bh = m_BroadcastHandlers.front();
675 m_BroadcastHandlers.pop();
676 bh->m_pHandler = handler;
680 handler->OnMenuStart(menu);
682 unsigned int total = 0;
683 for (unsigned int i=0; i<numClients; i++)
685 /* Only continue if displaying works */
686 if (!menu->Display(clients[i], bh, time))
691 /* :TODO: Allow sourcetv only, not all bots */
692 CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
693 if (player->IsFakeClient())
703 /* End the broadcast here */
704 handler->OnMenuEnd(menu);
705 FreeBroadcastHandler(bh);
711 unsigned int MenuManager::VoteMenu(IBaseMenu *menu,
712 IMenuVoteHandler *handler,
714 unsigned int numClients,
719 if (m_VoteHandlers.empty())
721 vh = new VoteHandler(handler);
723 vh = m_VoteHandlers.front();
724 m_VoteHandlers.pop();
725 vh->m_pHandler = handler;
729 vh->Initialize(menu);
730 handler->OnMenuStart(menu);
732 unsigned int total = 0;
733 for (unsigned int i=0; i<numClients; i++)
735 /* Only continue if displaying works */
736 if (!menu->Display(clients[i], vh, time))
741 /* :TODO: Allow sourcetv only, not all bots */
742 CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
743 if (player->IsFakeClient())
753 /* End the broadcast here */
754 handler->OnMenuEnd(menu);
762 void MenuManager::FreeBroadcastHandler(BroadcastHandler *bh)
764 m_BroadcastHandlers.push(bh);
767 void MenuManager::FreeVoteHandler(VoteHandler *vh)
769 m_VoteHandlers.push(vh);
772 IMenuStyle *MenuManager::GetDefaultStyle()
774 return m_pDefaultStyle;