3 * =============================================================================
5 * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved.
6 * =============================================================================
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, version 3.0, as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to link the
21 * code of this program (as well as its derivative works) to "Half-Life 2," the
22 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 * by the Valve Corporation. You must obey the GNU General Public License in
24 * all respects for all other code used. Additionally, AlliedModders LLC grants
25 * this exception to all derivative works. AlliedModders LLC defines further
26 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 * or <http://www.sourcemod.net/license.php>.
35 #include "MenuManager.h"
36 #include "MenuVoting.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"
46 VoteMenuHandler s_VoteHandler;
48 ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds");
50 MenuManager::MenuManager()
52 m_Styles.push_back(&g_ValveMenuStyle);
53 SetDefaultStyle(&g_ValveMenuStyle);
56 void MenuManager::OnSourceModAllInitialized()
58 g_ShareSys.AddInterface(NULL, this);
61 g_HandleSys.InitAccessDefaults(NULL, &access);
63 /* Deny cloning to menus */
64 access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
65 m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
67 /* Also deny deletion to styles */
68 access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
69 m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
72 void MenuManager::OnSourceModAllShutdown()
74 g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
75 g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
78 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
80 if (type == m_MenuType)
82 IBaseMenu *menu = (IBaseMenu *)object;
84 } else if (type == m_StyleType) {
89 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
91 if (m_MenuType == NO_HANDLE_TYPE)
96 return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
99 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
101 if (m_StyleType == NO_HANDLE_TYPE)
106 return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
109 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
113 sec.pIdentity = g_pCoreIdent;
116 return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
119 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
123 sec.pIdentity = g_pCoreIdent;
124 sec.pOwner = g_pCoreIdent;
126 return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
129 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
136 m_pDefaultStyle = style;
141 IMenuStyle *MenuManager::GetStyle(unsigned int index)
143 if (index >= GetStyleCount())
148 return m_Styles[index];
151 void MenuManager::AddStyle(IMenuStyle *style)
153 m_Styles.push_back(style);
156 unsigned int MenuManager::GetStyleCount()
158 return (unsigned int)m_Styles.size();
161 IMenuStyle *MenuManager::FindStyleByName(const char *name)
163 unsigned int count = GetStyleCount();
164 for (unsigned int i=0; i<count; i++)
166 IMenuStyle *ptr = GetStyle(i);
167 if (strcasecmp(ptr->GetStyleName(), name) == 0)
176 inline bool IsSlotItem(IMenuPanel *display,
179 if (!display->CanDrawItem(style))
183 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
187 if (style & ITEMDRAW_RAWLINE)
195 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
197 IBaseMenu *menu = md.menu;
206 unsigned int position;
210 /* Figure out how many items to draw */
211 IMenuStyle *style = menu->GetDrawStyle();
212 unsigned int pgn = menu->GetPagination();
213 unsigned int maxItems = style->GetMaxPageItems();
214 if (pgn != MENU_NO_PAGINATION)
219 unsigned int totalItems = menu->GetItemCount();
220 unsigned int startItem = 0;
222 /* For pagination, find the starting point. */
223 if (pgn != MENU_NO_PAGINATION)
225 if (order == ItemOrder_Ascending)
227 startItem = md.lastItem;
228 /* This shouldn't happen with well-coded menus.
229 * If the item is out of bounds, switch the order to
230 * Items_Descending and make us start from the top.
232 if (startItem >= totalItems)
234 startItem = totalItems - 1;
235 order = ItemOrder_Descending;
237 } else if (order == ItemOrder_Descending) {
238 startItem = md.firstItem;
239 /* This shouldn't happen with well-coded menus.
240 * If searching backwards doesn't give us enough room,
241 * start from the beginning and change to ascending.
243 if (startItem <= maxItems)
246 order = ItemOrder_Ascending;
251 /* Get our Display pointer and initialize some crap */
252 IMenuPanel *display = menu->CreatePanel();
253 IMenuHandler *mh = md.mh;
254 bool foundExtra = false;
255 unsigned int extraItem = 0;
258 * We keep searching until:
259 * 1) There are no more items
260 * 2) We reach one OVER the maximum number of slot items
261 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
263 unsigned int i = startItem;
264 unsigned int foundItems = 0;
267 ItemDrawInfo &dr = drawItems[foundItems].draw;
268 /* Is the item valid? */
269 if (menu->GetItemInfo(i, &dr) != NULL)
271 /* Ask the user to change the style, if necessary */
272 mh->OnMenuDrawItem(menu, client, i, dr.style);
273 /* Check if it's renderable */
274 if (IsSlotItem(display, dr.style))
276 /* If we've already found the max number of items,
277 * This means we should just cancel out and log our
280 if (foundItems >= maxItems)
286 drawItems[foundItems++].position = i;
289 /* If there's no pagination, stop once the menu is full. */
290 if (pgn == MENU_NO_PAGINATION)
292 /* If we've filled up, then stop */
293 if (foundItems >= maxItems)
298 /* If we're descending and this is the first item, stop */
299 if (order == ItemOrder_Descending)
307 /* If we're ascending and this is the last item, stop */
308 else if (order == ItemOrder_Ascending)
310 if (i >= totalItems - 1)
318 /* There were no items to draw! */
321 display->DeleteThis();
325 /* Check initial buttons */
326 bool displayPrev = false;
327 bool displayNext = false;
330 if (order == ItemOrder_Descending)
333 md.firstItem = extraItem;
334 } else if (order == ItemOrder_Ascending) {
336 md.lastItem = extraItem;
341 * If we're paginated, we have to find if there is another page.
342 * Sadly, the only way to do this is to try drawing more items!
344 if (pgn != MENU_NO_PAGINATION)
346 unsigned int lastItem = 0;
348 /* Find the last feasible item to search from. */
349 if (order == ItemOrder_Descending)
351 lastItem = drawItems[0].position;
352 if (lastItem >= totalItems - 1)
356 while (++lastItem < totalItems)
358 if (menu->GetItemInfo(lastItem, &dr) != NULL)
360 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
361 if (IsSlotItem(display, dr.style))
364 md.lastItem = lastItem;
369 } else if (order == ItemOrder_Ascending) {
370 lastItem = drawItems[0].position;
376 while (lastItem != 0)
378 if (menu->GetItemInfo(lastItem, &dr) != NULL)
380 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
381 if (IsSlotItem(display, dr.style))
384 md.firstItem = lastItem;
394 /* Draw the item according to the order */
395 menu_slots_t *slots = md.slots;
396 unsigned int position = 0; /* Keep track of the last position */
397 if (order == ItemOrder_Ascending)
399 for (unsigned int i=0; i<foundItems; i++)
401 ItemDrawInfo &dr = drawItems[i].draw;
402 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
404 position = display->DrawItem(dr);
408 slots[position].item = drawItems[i].position;
409 slots[position].type = ItemSel_Item;
412 } else if (order == ItemOrder_Descending) {
413 unsigned int i = foundItems;
414 /* NOTE: There will always be at least one item because
415 * of the check earlier.
419 ItemDrawInfo &dr = drawItems[i].draw;
420 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
422 position = display->DrawItem(dr);
426 slots[position].item = drawItems[i].position;
427 slots[position].type = ItemSel_Item;
432 /* Now, we need to check if we need to add anything extra */
433 if (pgn != MENU_NO_PAGINATION)
435 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
436 bool exitButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT;
437 bool exitBackButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXITBACK;
440 /* Calculate how many items we are allowed for control stuff */
441 unsigned int padding = style->GetMaxPageItems() - maxItems;
443 /* Add the number of available slots */
444 padding += (maxItems - foundItems);
446 /* Someday, if we are able to re-enable this, we will be very lucky men. */
448 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
451 /* Even if we don't draw an exit button, we invalidate the slot. */
455 /* Otherwise, we don't draw anything and leave the slot available */
460 /* Subtract two slots for the displayNext/displayPrev padding */
463 /* If we have an "Exit Back" button and the space to draw it, do so. */
470 exitBackButton = false;
475 * We allow next/prev to be undrawn if neither exists.
476 * Thus, we only need padding if one of them will be drawn,
477 * or the exit button will be drawn.
479 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
480 if (exitButton || (displayNext || displayPrev))
482 /* If there are no control options,
483 * Instead just pad with invisible slots.
485 if (!displayPrev && !displayPrev)
487 padItem.style = ITEMDRAW_NOTEXT;
489 /* Add spacers so we can pad to the end */
490 for (unsigned int i=0; i<padding; i++)
492 position = display->DrawItem(padItem);
493 slots[position].type = ItemSel_None;
497 /* Put a fake spacer before control stuff, if possible */
498 if ((displayPrev || displayNext) || exitButton)
500 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
501 display->DrawItem(draw);
504 ItemDrawInfo dr(text, 0);
506 * If we have one or the other, we need to have spacers for both.
508 if (displayPrev || displayNext)
511 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
512 if (displayPrev || canDrawDisabled)
516 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
517 dr.style = ITEMDRAW_CONTROL;
518 position = display->DrawItem(dr);
519 slots[position].type = ItemSel_ExitBack;
521 CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL);
522 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
523 position = display->DrawItem(dr);
524 slots[position].type = ItemSel_Back;
526 } else if (displayNext || exitButton) {
527 /* If we can't display this, and there is an exit button,
530 position = display->DrawItem(padCtrlItem);
531 slots[position].type = ItemSel_None;
535 if (displayNext || canDrawDisabled)
537 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
538 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
539 position = display->DrawItem(dr);
540 slots[position].type = ItemSel_Next;
541 } else if (exitButton) {
542 /* If we can't display this,
543 * but there is an "exit" button, we need to pad!
545 position = display->DrawItem(padCtrlItem);
546 slots[position].type = ItemSel_None;
549 /* Otherwise, bump to two slots! */
550 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
551 position = display->DrawItem(numBump);
552 slots[position].type = ItemSel_None;
553 position = display->DrawItem(numBump);
554 slots[position].type = ItemSel_None;
560 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
561 dr.style = ITEMDRAW_CONTROL;
562 position = display->DrawItem(dr);
563 slots[position].type = ItemSel_Exit;
567 /* Lastly, fill in any slots we could have missed */
568 for (unsigned int i = position + 1; i < 10; i++)
570 slots[i].type = ItemSel_None;
574 mh->OnMenuDisplay(menu, client, display);
575 display->DrawTitle(menu->GetDefaultTitle(), true);
580 IMenuStyle *MenuManager::GetDefaultStyle()
582 return m_pDefaultStyle;
585 bool MenuManager::MenuSoundsEnabled()
587 return (sm_menu_sounds.GetInt() != 0);
590 ConfigResult MenuManager::OnSourceModConfigChanged(const char *key,
596 if (strcmp(key, "MenuItemSound") == 0)
598 m_SelectSound.assign(value);
599 return ConfigResult_Accept;
600 } else if (strcmp(key, "MenuExitBackSound") == 0) {
601 m_ExitBackSound.assign(value);
602 return ConfigResult_Accept;
603 } else if (strcmp(key, "MenuExitSound") == 0) {
604 m_ExitSound.assign(value);
605 return ConfigResult_Accept;
608 return ConfigResult_Ignore;
611 const char *MenuManager::GetMenuSound(ItemSelection sel)
613 const char *sound = NULL;
621 if (m_SelectSound.size() > 0)
623 sound = m_SelectSound.c_str();
627 case ItemSel_ExitBack:
629 if (m_ExitBackSound.size() > 0)
631 sound = m_ExitBackSound.c_str();
637 if (m_ExitSound.size() > 0)
639 sound = m_ExitSound.c_str();
652 void MenuManager::OnSourceModLevelChange(const char *mapName)
654 if (m_SelectSound.size() > 0)
656 enginesound->PrecacheSound(m_SelectSound.c_str(), true);
658 if (m_ExitBackSound.size() > 0)
660 enginesound->PrecacheSound(m_ExitBackSound.c_str(), true);
662 if (m_ExitSound.size() > 0)
664 enginesound->PrecacheSound(m_ExitSound.c_str(), true);
668 void MenuManager::CancelMenu(IBaseMenu *menu)
670 if (s_VoteHandler.GetCurrentMenu() == menu
671 && !s_VoteHandler.IsCancelling())
673 s_VoteHandler.CancelVoting();
680 bool MenuManager::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags)
682 return s_VoteHandler.StartVote(menu, num_clients, clients, max_time, flags);
685 bool MenuManager::IsVoteInProgress()
687 return s_VoteHandler.IsVoteInProgress();
690 void MenuManager::CancelVoting()
692 s_VoteHandler.CancelVoting();
695 unsigned int MenuManager::GetRemainingVoteDelay()
697 return s_VoteHandler.GetRemainingVoteDelay();