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"
26 /*************************************
27 *************************************
28 **** BROADCAST HANDLING WRAPPERS ****
29 *************************************
30 *************************************/
32 BroadcastHandler::BroadcastHandler(IMenuHandler *handler) : m_pHandler(handler), numClients(0)
36 unsigned int BroadcastHandler::GetMenuAPIVersion2()
38 return m_pHandler->GetMenuAPIVersion2();
41 void BroadcastHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
43 m_pHandler->OnMenuCancel(menu, client, reason);
46 void BroadcastHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuDisplay *display)
49 m_pHandler->OnMenuDisplay(menu, client, display);
52 void BroadcastHandler::OnBroadcastEnd(IBaseMenu *menu)
54 g_Menus.FreeBroadcastHandler(this);
57 void BroadcastHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
59 m_pHandler->OnMenuSelect(menu, client, item);
62 void BroadcastHandler::OnMenuEnd(IBaseMenu *menu)
64 assert(numClients > 0);
66 /* Only fire if all clients have gotten a menu end */
67 if (--numClients == 0)
69 IMenuHandler *pHandler = m_pHandler;
71 pHandler->OnMenuEnd(menu);
75 VoteHandler::VoteHandler(IMenuVoteHandler *handler)
76 : BroadcastHandler(handler), m_pVoteHandler(handler)
80 void VoteHandler::Initialize(IBaseMenu *menu)
82 unsigned int numItems = menu->GetItemCount();
84 if (m_counts.size() >= numItems)
86 for (size_t i=0; i<numItems; i++)
91 for (size_t i=0; i<m_counts.size(); i++)
95 m_counts.resize(numItems, 0);
99 void VoteHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
106 BroadcastHandler::OnMenuSelect(menu, client, item);
109 void VoteHandler::OnBroadcastEnd(IBaseMenu *menu)
114 for (size_t i=1; i<numItems; i++)
116 if (m_counts[i] > m_counts[highest])
120 } else if (m_counts[i] == m_counts[highest]) {
127 m_ties.push_back(highest);
128 srand(static_cast<unsigned int>(time(NULL)));
129 highest = m_ties[rand() % m_ties.size()];
132 m_pVoteHandler->OnMenuVoteEnd(menu, highest);
134 g_Menus.FreeVoteHandler(this);
137 /*******************************
138 *******************************
139 ******** MENU MANAGER *********
140 *******************************
141 *******************************/
143 MenuManager::MenuManager()
146 m_pDefaultStyle = NULL;
149 void MenuManager::OnSourceModAllInitialized()
151 int num = g_SMAPI->GetUserMessageCount();
154 for (int i=0; i<num; i++)
156 if (strcmp(g_SMAPI->GetUserMessage(i, NULL), "ShowMenu") == 0)
165 m_Styles.push_back(&g_ValveMenuStyle);
166 SetDefaultStyle(&g_ValveMenuStyle);
169 void MenuManager::OnSourceModAllShutdown()
171 while (!m_BroadcastHandlers.empty())
173 delete m_BroadcastHandlers.front();
174 m_BroadcastHandlers.pop();
177 while (!m_VoteHandlers.empty())
179 delete m_VoteHandlers.front();
180 m_VoteHandlers.pop();
184 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
191 m_pDefaultStyle = style;
196 IMenuStyle *MenuManager::GetStyle(unsigned int index)
198 if (index >= GetStyleCount())
203 return m_Styles[index];
206 unsigned int MenuManager::GetStyleCount()
208 return (unsigned int)m_Styles.size();
211 IMenuStyle *MenuManager::FindStyleByName(const char *name)
213 unsigned int count = GetStyleCount();
214 for (unsigned int i=0; i<count; i++)
216 IMenuStyle *ptr = GetStyle(i);
217 if (strcasecmp(ptr->GetStyleName(), name) == 0)
226 inline bool IsSlotItem(IMenuDisplay *display,
229 if (!display->CanDrawItem(style))
233 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
237 if (style & ITEMDRAW_RAWLINE)
245 IMenuDisplay *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
247 IBaseMenu *menu = md.menu;
256 unsigned int position;
260 /* Figure out how many items to draw */
261 IMenuStyle *style = menu->GetDrawStyle();
262 unsigned int pgn = menu->GetPagination();
263 unsigned int maxItems = style->GetMaxPageItems();
264 if (pgn != MENU_NO_PAGINATION)
269 unsigned int totalItems = menu->GetItemCount();
270 unsigned int startItem = 0;
272 /* For pagination, find the starting point. */
273 if (pgn != MENU_NO_PAGINATION)
275 if (order == ItemOrder_Ascending)
277 startItem = md.lastItem;
278 /* This shouldn't happen with well-coded menus.
279 * If the item is out of bounds, switch the order to
280 * Items_Descending and make us start from the top.
282 if (startItem >= totalItems)
284 startItem = totalItems - 1;
285 order = ItemOrder_Descending;
287 } else if (order == ItemOrder_Descending) {
288 startItem = md.firstItem;
289 /* This shouldn't happen with well-coded menus.
290 * If searching backwards doesn't give us enough room,
291 * start from the beginning and change to ascending.
293 if (startItem <= maxItems)
296 order = ItemOrder_Ascending;
301 /* Get our Display pointer and initialize some crap */
302 IMenuDisplay *display = menu->CreateDisplay();
303 IMenuHandler *mh = md.mh;
304 bool foundExtra = false;
305 unsigned int extraItem = 0;
308 * We keep searching until:
309 * 1) There are no more items
310 * 2) We reach one OVER the maximum number of slot items
311 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
313 unsigned int i = startItem;
314 unsigned int foundItems = 0;
317 ItemDrawInfo &dr = drawItems[foundItems].draw;
318 /* Is the item valid? */
319 if (menu->GetItemInfo(i, &dr) != NULL)
321 /* Ask the user to change the style, if necessary */
322 mh->OnMenuDrawItem(menu, client, i, dr.style);
323 /* Check if it's renderable */
324 if (IsSlotItem(display, dr.style))
326 /* If we've already found the max number of items,
327 * This means we should just cancel out and log our
330 if (foundItems >= maxItems)
336 drawItems[foundItems++].position = i;
339 /* If there's no pagination, stop once the menu is full. */
340 if (pgn == MENU_NO_PAGINATION)
342 /* If we've filled up, then stop */
343 if (foundItems >= maxItems)
348 /* If we're descending and this is the first item, stop */
349 if (order == ItemOrder_Descending)
357 /* If we're ascending and this is the last item, stop */
358 else if (order == ItemOrder_Ascending)
360 if (i >= totalItems - 1)
368 /* There were no items to draw! */
371 display->DeleteThis();
375 /* Check initial buttons */
376 bool displayPrev = false;
377 bool displayNext = false;
380 if (order == ItemOrder_Descending)
383 md.firstItem = extraItem;
384 } else if (order == ItemOrder_Ascending) {
386 md.lastItem = extraItem;
391 * If we're paginated, we have to find if there is another page.
392 * Sadly, the only way to do this is to try drawing more items!
394 if (pgn != MENU_NO_PAGINATION)
396 unsigned int lastItem = 0;
398 /* Find the last feasible item to search from. */
399 if (order == ItemOrder_Descending)
401 lastItem = drawItems[0].position;
402 if (lastItem >= totalItems - 1)
406 while (++lastItem < totalItems)
408 if (menu->GetItemInfo(lastItem, &dr) != NULL)
410 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
411 if (IsSlotItem(display, dr.style))
414 md.lastItem = lastItem;
419 } else if (order == ItemOrder_Ascending) {
420 lastItem = drawItems[0].position;
426 while (lastItem-- != 0)
428 if (menu->GetItemInfo(lastItem, &dr) != NULL)
430 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
431 if (IsSlotItem(display, dr.style))
434 md.firstItem = lastItem;
443 /* Draw the item according to the order */
444 menu_slots_t *slots = md.slots;
445 unsigned int position = 0; /* Keep track of the last position */
446 if (order == ItemOrder_Ascending)
448 for (unsigned int i=0; i<foundItems; i++)
450 ItemDrawInfo &dr = drawItems[i].draw;
451 menu->GetItemInfo(drawItems[i].position, &dr);
452 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
453 if ((position = display->DrawItem(dr)) != 0)
455 slots[position].item = drawItems[i].position;
456 slots[position].type = ItemSel_Item;
459 } else if (order == ItemOrder_Descending) {
460 unsigned int i = foundItems;
461 /* NOTE: There will always be at least one item because
462 * of the check earlier.
466 ItemDrawInfo &dr = drawItems[i].draw;
467 menu->GetItemInfo(drawItems[i].position, &dr);
468 mh->OnMenuDisplayItem(menu, client, drawItems[i].position, &(dr.display));
469 if ((position = display->DrawItem(dr)) != 0)
471 slots[position].item = drawItems[i].position;
472 slots[position].type = ItemSel_Item;
477 /* Now, we need to check if we need to add anything extra */
478 if (pgn != MENU_NO_PAGINATION)
480 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED);
481 bool exitButton = menu->GetExitButton();
484 /* Calculate how many items we are allowed for control stuff */
485 unsigned int padding = style->GetMaxPageItems() - maxItems;
487 /* Add the number of available slots */
488 padding += (maxItems - foundItems);
490 /* Someday, if we are able to re-enable this, we will be very lucky men. */
492 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
495 /* Even if we don't draw an exit button, we invalidate the slot. */
499 /* Otherwise, we don't draw anything and leave the slot available */
504 /* Subtract two slots for the displayNext/displayPrev padding */
508 * We allow next/prev to be undrawn if neither exists.
509 * Thus, we only need padding if one of them will be drawn,
510 * or the exit button will be drawn.
512 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
513 if (exitButton || (displayNext || displayPrev))
515 /* Add spacers so we can pad to the end */
516 for (unsigned int i=0; i<padding; i++)
518 position = display->DrawItem(padItem);
519 slots[position].type = ItemSel_None;
523 /* Put a fake spacer before control stuff, if possible */
525 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
526 display->DrawItem(draw);
530 ItemDrawInfo dr(text, 0);
531 if (displayPrev || canDrawDisabled)
533 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
534 dr.style = displayPrev ? 0 : ITEMDRAW_DISABLED;
535 position = display->DrawItem(dr);
536 slots[position].type = ItemSel_Back;
537 } else if ((displayNext || canDrawDisabled) || exitButton) {
538 /* If we can't display this,
539 * but there is a "next" or "exit" button, we need to pad!
541 position = display->DrawItem(padItem);
542 slots[position].type = ItemSel_None;
546 if (displayNext || canDrawDisabled)
548 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
549 dr.style = displayNext ? 0 : ITEMDRAW_DISABLED;
550 position = display->DrawItem(dr);
551 slots[position].type = ItemSel_Next;
552 } else if (exitButton) {
553 /* If we can't display this,
554 * but there is an exit button, we need to pad!
556 position = display->DrawItem(padItem);
557 slots[position].type = ItemSel_None;
563 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
565 position = display->DrawItem(dr);
566 slots[position].type = ItemSel_Exit;
570 /* Lastly, fill in any slots we could have missed */
571 for (unsigned int i = position + 1; i < 10; i++)
573 slots[i].type = ItemSel_None;
577 mh->OnMenuDisplay(menu, client, display);
578 display->DrawTitle(menu->GetDefaultTitle(), true);
583 unsigned int MenuManager::BroadcastMenu(IBaseMenu *menu,
584 IMenuHandler *handler,
586 unsigned int numClients,
589 BroadcastHandler *bh;
591 if (m_BroadcastHandlers.empty())
593 bh = new BroadcastHandler(handler);
595 bh = m_BroadcastHandlers.front();
596 m_BroadcastHandlers.pop();
597 bh->m_pHandler = handler;
601 handler->OnMenuStart(menu);
603 unsigned int total = 0;
604 for (unsigned int i=0; i<numClients; i++)
606 /* Only continue if displaying works */
607 if (!menu->Display(clients[i], bh, time))
612 /* :TODO: Allow sourcetv only, not all bots */
613 CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
614 if (player->IsFakeClient())
624 /* End the broadcast here */
625 handler->OnMenuEnd(menu);
626 FreeBroadcastHandler(bh);
632 unsigned int MenuManager::VoteMenu(IBaseMenu *menu,
633 IMenuVoteHandler *handler,
635 unsigned int numClients,
640 if (m_VoteHandlers.empty())
642 vh = new VoteHandler(handler);
644 vh = m_VoteHandlers.front();
645 m_VoteHandlers.pop();
646 vh->m_pHandler = handler;
650 vh->Initialize(menu);
651 handler->OnMenuStart(menu);
653 unsigned int total = 0;
654 for (unsigned int i=0; i<numClients; i++)
656 /* Only continue if displaying works */
657 if (!menu->Display(clients[i], vh, time))
662 /* :TODO: Allow sourcetv only, not all bots */
663 CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
664 if (player->IsFakeClient())
674 /* End the broadcast here */
675 handler->OnMenuEnd(menu);
682 void MenuManager::FreeBroadcastHandler(BroadcastHandler *bh)
684 m_BroadcastHandlers.push(bh);
687 void MenuManager::FreeVoteHandler(VoteHandler *vh)
689 m_VoteHandlers.push(vh);
692 IMenuStyle *MenuManager::GetDefaultStyle()
694 return m_pDefaultStyle;