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"
27 /*************************************
28 *************************************
29 **** BROADCAST HANDLING WRAPPERS ****
30 *************************************
31 *************************************/
33 BroadcastHandler::BroadcastHandler(IMenuHandler *handler) : m_pHandler(handler), numClients(0)
37 unsigned int BroadcastHandler::GetMenuAPIVersion2()
39 return m_pHandler->GetMenuAPIVersion2();
42 void BroadcastHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
44 m_pHandler->OnMenuCancel(menu, client, reason);
47 void BroadcastHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
50 m_pHandler->OnMenuDisplay(menu, client, display);
53 void BroadcastHandler::OnBroadcastEnd(IBaseMenu *menu)
55 g_Menus.FreeBroadcastHandler(this);
58 void BroadcastHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
60 m_pHandler->OnMenuSelect(menu, client, item);
63 void BroadcastHandler::OnMenuEnd(IBaseMenu *menu)
65 assert(numClients > 0);
67 /* Only fire if all clients have gotten a menu end */
68 if (--numClients == 0)
70 IMenuHandler *pHandler = m_pHandler;
72 pHandler->OnMenuEnd(menu);
76 VoteHandler::VoteHandler(IMenuVoteHandler *handler)
77 : BroadcastHandler(handler), m_pVoteHandler(handler)
81 void VoteHandler::Initialize(IBaseMenu *menu)
83 unsigned int numItems = menu->GetItemCount();
85 if (m_counts.size() >= numItems)
87 for (size_t i=0; i<numItems; i++)
92 for (size_t i=0; i<m_counts.size(); i++)
96 m_counts.resize(numItems, 0);
100 void VoteHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
107 BroadcastHandler::OnMenuSelect(menu, client, item);
110 void VoteHandler::OnBroadcastEnd(IBaseMenu *menu)
115 for (size_t i=1; i<numItems; i++)
117 if (m_counts[i] > m_counts[highest])
121 } else if (m_counts[i] == m_counts[highest]) {
128 m_ties.push_back(highest);
129 srand(static_cast<unsigned int>(time(NULL)));
130 highest = m_ties[rand() % m_ties.size()];
133 m_pVoteHandler->OnMenuVoteEnd(menu, highest);
135 g_Menus.FreeVoteHandler(this);
138 /*******************************
139 *******************************
140 ******** MENU MANAGER *********
141 *******************************
142 *******************************/
144 MenuManager::MenuManager()
146 m_Styles.push_back(&g_ValveMenuStyle);
147 SetDefaultStyle(&g_ValveMenuStyle);
150 void MenuManager::OnSourceModAllInitialized()
152 g_ShareSys.AddInterface(NULL, this);
155 void MenuManager::OnSourceModAllShutdown()
157 while (!m_BroadcastHandlers.empty())
159 delete m_BroadcastHandlers.front();
160 m_BroadcastHandlers.pop();
163 while (!m_VoteHandlers.empty())
165 delete m_VoteHandlers.front();
166 m_VoteHandlers.pop();
170 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
177 m_pDefaultStyle = style;
182 IMenuStyle *MenuManager::GetStyle(unsigned int index)
184 if (index >= GetStyleCount())
189 return m_Styles[index];
192 void MenuManager::AddStyle(IMenuStyle *style)
194 m_Styles.push_back(style);
197 unsigned int MenuManager::GetStyleCount()
199 return (unsigned int)m_Styles.size();
202 IMenuStyle *MenuManager::FindStyleByName(const char *name)
204 unsigned int count = GetStyleCount();
205 for (unsigned int i=0; i<count; i++)
207 IMenuStyle *ptr = GetStyle(i);
208 if (strcasecmp(ptr->GetStyleName(), name) == 0)
217 inline bool IsSlotItem(IMenuPanel *display,
220 if (!display->CanDrawItem(style))
224 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
228 if (style & ITEMDRAW_RAWLINE)
236 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
238 IBaseMenu *menu = md.menu;
247 unsigned int position;
251 /* Figure out how many items to draw */
252 IMenuStyle *style = menu->GetDrawStyle();
253 unsigned int pgn = menu->GetPagination();
254 unsigned int maxItems = style->GetMaxPageItems();
255 if (pgn != MENU_NO_PAGINATION)
260 unsigned int totalItems = menu->GetItemCount();
261 unsigned int startItem = 0;
263 /* For pagination, find the starting point. */
264 if (pgn != MENU_NO_PAGINATION)
266 if (order == ItemOrder_Ascending)
268 startItem = md.lastItem;
269 /* This shouldn't happen with well-coded menus.
270 * If the item is out of bounds, switch the order to
271 * Items_Descending and make us start from the top.
273 if (startItem >= totalItems)
275 startItem = totalItems - 1;
276 order = ItemOrder_Descending;
278 } else if (order == ItemOrder_Descending) {
279 startItem = md.firstItem;
280 /* This shouldn't happen with well-coded menus.
281 * If searching backwards doesn't give us enough room,
282 * start from the beginning and change to ascending.
284 if (startItem <= maxItems)
287 order = ItemOrder_Ascending;
292 /* Get our Display pointer and initialize some crap */
293 IMenuPanel *display = menu->CreatePanel();
294 IMenuHandler *mh = md.mh;
295 bool foundExtra = false;
296 unsigned int extraItem = 0;
299 * We keep searching until:
300 * 1) There are no more items
301 * 2) We reach one OVER the maximum number of slot items
302 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
304 unsigned int i = startItem;
305 unsigned int foundItems = 0;
308 ItemDrawInfo &dr = drawItems[foundItems].draw;
309 /* Is the item valid? */
310 if (menu->GetItemInfo(i, &dr) != NULL)
312 /* Ask the user to change the style, if necessary */
313 mh->OnMenuDrawItem(menu, client, i, dr.style);
314 /* Check if it's renderable */
315 if (IsSlotItem(display, dr.style))
317 /* If we've already found the max number of items,
318 * This means we should just cancel out and log our
321 if (foundItems >= maxItems)
327 drawItems[foundItems++].position = i;
330 /* If there's no pagination, stop once the menu is full. */
331 if (pgn == MENU_NO_PAGINATION)
333 /* If we've filled up, then stop */
334 if (foundItems >= maxItems)
339 /* If we're descending and this is the first item, stop */
340 if (order == ItemOrder_Descending)
348 /* If we're ascending and this is the last item, stop */
349 else if (order == ItemOrder_Ascending)
351 if (i >= totalItems - 1)
359 /* There were no items to draw! */
362 display->DeleteThis();
366 /* Check initial buttons */
367 bool displayPrev = false;
368 bool displayNext = false;
371 if (order == ItemOrder_Descending)
374 md.firstItem = extraItem;
375 } else if (order == ItemOrder_Ascending) {
377 md.lastItem = extraItem;
382 * If we're paginated, we have to find if there is another page.
383 * Sadly, the only way to do this is to try drawing more items!
385 if (pgn != MENU_NO_PAGINATION)
387 unsigned int lastItem = 0;
389 /* Find the last feasible item to search from. */
390 if (order == ItemOrder_Descending)
392 lastItem = drawItems[0].position;
393 if (lastItem >= totalItems - 1)
397 while (++lastItem < totalItems)
399 if (menu->GetItemInfo(lastItem, &dr) != NULL)
401 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
402 if (IsSlotItem(display, dr.style))
405 md.lastItem = lastItem;
410 } else if (order == ItemOrder_Ascending) {
411 lastItem = drawItems[0].position;
417 while (lastItem-- != 0)
419 if (menu->GetItemInfo(lastItem, &dr) != NULL)
421 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
422 if (IsSlotItem(display, dr.style))
425 md.firstItem = lastItem;
434 /* Draw the item according to the order */
435 menu_slots_t *slots = md.slots;
436 unsigned int position = 0; /* Keep track of the last position */
437 if (order == ItemOrder_Ascending)
439 for (unsigned int i=0; i<foundItems; i++)
441 ItemDrawInfo &dr = drawItems[i].draw;
442 menu->GetItemInfo(drawItems[i].position, &dr);
443 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
444 if ((position = display->DrawItem(dr)) != 0)
446 slots[position].item = drawItems[i].position;
447 slots[position].type = ItemSel_Item;
450 } else if (order == ItemOrder_Descending) {
451 unsigned int i = foundItems;
452 /* NOTE: There will always be at least one item because
453 * of the check earlier.
457 ItemDrawInfo &dr = drawItems[i].draw;
458 menu->GetItemInfo(drawItems[i].position, &dr);
459 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
460 if ((position = display->DrawItem(dr)) != 0)
462 slots[position].item = drawItems[i].position;
463 slots[position].type = ItemSel_Item;
468 /* Now, we need to check if we need to add anything extra */
469 if (pgn != MENU_NO_PAGINATION)
471 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
472 bool exitButton = menu->GetExitButton();
475 /* Calculate how many items we are allowed for control stuff */
476 unsigned int padding = style->GetMaxPageItems() - maxItems;
478 /* Add the number of available slots */
479 padding += (maxItems - foundItems);
481 /* Someday, if we are able to re-enable this, we will be very lucky men. */
483 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
486 /* Even if we don't draw an exit button, we invalidate the slot. */
490 /* Otherwise, we don't draw anything and leave the slot available */
495 /* Subtract two slots for the displayNext/displayPrev padding */
499 * We allow next/prev to be undrawn if neither exists.
500 * Thus, we only need padding if one of them will be drawn,
501 * or the exit button will be drawn.
503 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
504 if (exitButton || (displayNext || displayPrev))
506 /* Add spacers so we can pad to the end */
507 for (unsigned int i=0; i<padding; i++)
509 position = display->DrawItem(padItem);
510 slots[position].type = ItemSel_None;
514 /* Put a fake spacer before control stuff, if possible */
516 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
517 display->DrawItem(draw);
520 ItemDrawInfo dr(text, 0);
522 * If we have one or the other, we need to have spacers for both.
524 if (displayPrev || displayNext)
527 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
528 if (displayPrev || canDrawDisabled)
530 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
531 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
532 position = display->DrawItem(dr);
533 slots[position].type = ItemSel_Back;
534 } else if (displayNext || exitButton) {
535 /* If we can't display this, and there is an exit button,
538 position = display->DrawItem(padCtrlItem);
539 slots[position].type = ItemSel_None;
543 if (displayNext || canDrawDisabled)
545 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
546 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
547 position = display->DrawItem(dr);
548 slots[position].type = ItemSel_Next;
549 } else if (exitButton) {
550 /* If we can't display this,
551 * but there is an "exit" button, we need to pad!
553 position = display->DrawItem(padCtrlItem);
554 slots[position].type = ItemSel_None;
557 /* Otherwise, bump to two slots! */
558 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
559 position = display->DrawItem(numBump);
560 slots[position].type = ItemSel_None;
561 position = display->DrawItem(numBump);
562 slots[position].type = ItemSel_None;
568 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
569 dr.style = ITEMDRAW_CONTROL;
570 position = display->DrawItem(dr);
571 slots[position].type = ItemSel_Exit;
575 /* Lastly, fill in any slots we could have missed */
576 for (unsigned int i = position + 1; i < 10; i++)
578 slots[i].type = ItemSel_None;
582 mh->OnMenuDisplay(menu, client, display);
583 display->DrawTitle(menu->GetDefaultTitle(), true);
588 unsigned int MenuManager::BroadcastMenu(IBaseMenu *menu,
589 IMenuHandler *handler,
591 unsigned int numClients,
594 BroadcastHandler *bh;
596 if (m_BroadcastHandlers.empty())
598 bh = new BroadcastHandler(handler);
600 bh = m_BroadcastHandlers.front();
601 m_BroadcastHandlers.pop();
602 bh->m_pHandler = handler;
606 handler->OnMenuStart(menu);
608 unsigned int total = 0;
609 for (unsigned int i=0; i<numClients; i++)
611 /* Only continue if displaying works */
612 if (!menu->Display(clients[i], bh, time))
617 /* :TODO: Allow sourcetv only, not all bots */
618 CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
619 if (player->IsFakeClient())
629 /* End the broadcast here */
630 handler->OnMenuEnd(menu);
631 FreeBroadcastHandler(bh);
637 unsigned int MenuManager::VoteMenu(IBaseMenu *menu,
638 IMenuVoteHandler *handler,
640 unsigned int numClients,
645 if (m_VoteHandlers.empty())
647 vh = new VoteHandler(handler);
649 vh = m_VoteHandlers.front();
650 m_VoteHandlers.pop();
651 vh->m_pHandler = handler;
655 vh->Initialize(menu);
656 handler->OnMenuStart(menu);
658 unsigned int total = 0;
659 for (unsigned int i=0; i<numClients; i++)
661 /* Only continue if displaying works */
662 if (!menu->Display(clients[i], vh, time))
667 /* :TODO: Allow sourcetv only, not all bots */
668 CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
669 if (player->IsFakeClient())
679 /* End the broadcast here */
680 handler->OnMenuEnd(menu);
687 void MenuManager::FreeBroadcastHandler(BroadcastHandler *bh)
689 m_BroadcastHandlers.push(bh);
692 void MenuManager::FreeVoteHandler(VoteHandler *vh)
694 m_VoteHandlers.push(vh);
697 IMenuStyle *MenuManager::GetDefaultStyle()
699 return m_pDefaultStyle;