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 "MenuVoting.h"
38 #include "sm_stringutil.h"
39 #include "sourcemm_api.h"
40 #include "PlayerManager.h"
41 #include "MenuStyle_Valve.h"
43 #include "HandleSys.h"
44 #include "sourcemm_api.h"
47 VoteMenuHandler s_VoteHandler;
49 ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds");
51 MenuManager::MenuManager()
53 m_Styles.push_back(&g_ValveMenuStyle);
54 SetDefaultStyle(&g_ValveMenuStyle);
57 void MenuManager::OnSourceModAllInitialized()
59 g_ShareSys.AddInterface(NULL, this);
62 g_HandleSys.InitAccessDefaults(NULL, &access);
64 /* Deny cloning to menus */
65 access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
66 m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL);
68 /* Also deny deletion to styles */
69 access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY;
70 m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL);
73 void MenuManager::OnSourceModAllShutdown()
75 g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
76 g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
79 void MenuManager::OnHandleDestroy(HandleType_t type, void *object)
81 if (type == m_MenuType)
83 IBaseMenu *menu = (IBaseMenu *)object;
85 } else if (type == m_StyleType) {
90 Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner)
92 if (m_MenuType == NO_HANDLE_TYPE)
97 return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL);
100 Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style)
102 if (m_StyleType == NO_HANDLE_TYPE)
107 return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL);
110 HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu)
114 sec.pIdentity = g_pCoreIdent;
117 return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu);
120 HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style)
124 sec.pIdentity = g_pCoreIdent;
125 sec.pOwner = g_pCoreIdent;
127 return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style);
130 bool MenuManager::SetDefaultStyle(IMenuStyle *style)
137 m_pDefaultStyle = style;
142 IMenuStyle *MenuManager::GetStyle(unsigned int index)
144 if (index >= GetStyleCount())
149 return m_Styles[index];
152 void MenuManager::AddStyle(IMenuStyle *style)
154 m_Styles.push_back(style);
157 unsigned int MenuManager::GetStyleCount()
159 return (unsigned int)m_Styles.size();
162 IMenuStyle *MenuManager::FindStyleByName(const char *name)
164 unsigned int count = GetStyleCount();
165 for (unsigned int i=0; i<count; i++)
167 IMenuStyle *ptr = GetStyle(i);
168 if (strcasecmp(ptr->GetStyleName(), name) == 0)
177 inline bool IsSlotItem(IMenuPanel *display,
180 if (!display->CanDrawItem(style))
184 if ((style & ITEMDRAW_IGNORE) == ITEMDRAW_IGNORE)
188 if (style & ITEMDRAW_RAWLINE)
196 IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder order)
198 IBaseMenu *menu = md.menu;
207 unsigned int position;
211 /* Figure out how many items to draw */
212 IMenuStyle *style = menu->GetDrawStyle();
213 unsigned int pgn = menu->GetPagination();
214 unsigned int maxItems = style->GetMaxPageItems();
215 if (pgn != MENU_NO_PAGINATION)
220 unsigned int totalItems = menu->GetItemCount();
221 unsigned int startItem = 0;
223 /* For pagination, find the starting point. */
224 if (pgn != MENU_NO_PAGINATION)
226 if (order == ItemOrder_Ascending)
228 startItem = md.lastItem;
229 /* This shouldn't happen with well-coded menus.
230 * If the item is out of bounds, switch the order to
231 * Items_Descending and make us start from the top.
233 if (startItem >= totalItems)
235 startItem = totalItems - 1;
236 order = ItemOrder_Descending;
238 } else if (order == ItemOrder_Descending) {
239 startItem = md.firstItem;
240 /* This shouldn't happen with well-coded menus.
241 * If searching backwards doesn't give us enough room,
242 * start from the beginning and change to ascending.
244 if (startItem <= maxItems)
247 order = ItemOrder_Ascending;
252 /* Get our Display pointer and initialize some crap */
253 IMenuPanel *display = menu->CreatePanel();
254 IMenuHandler *mh = md.mh;
255 bool foundExtra = false;
256 unsigned int extraItem = 0;
259 * We keep searching until:
260 * 1) There are no more items
261 * 2) We reach one OVER the maximum number of slot items
262 * 3) We have reached maxItems and pagination is MENU_NO_PAGINATION
264 unsigned int i = startItem;
265 unsigned int foundItems = 0;
268 ItemDrawInfo &dr = drawItems[foundItems].draw;
269 /* Is the item valid? */
270 if (menu->GetItemInfo(i, &dr) != NULL)
272 /* Ask the user to change the style, if necessary */
273 mh->OnMenuDrawItem(menu, client, i, dr.style);
274 /* Check if it's renderable */
275 if (IsSlotItem(display, dr.style))
277 /* If we've already found the max number of items,
278 * This means we should just cancel out and log our
281 if (foundItems >= maxItems)
287 drawItems[foundItems++].position = i;
290 /* If there's no pagination, stop once the menu is full. */
291 if (pgn == MENU_NO_PAGINATION)
293 /* If we've filled up, then stop */
294 if (foundItems >= maxItems)
299 /* If we're descending and this is the first item, stop */
300 if (order == ItemOrder_Descending)
308 /* If we're ascending and this is the last item, stop */
309 else if (order == ItemOrder_Ascending)
311 if (i >= totalItems - 1)
319 /* There were no items to draw! */
322 display->DeleteThis();
326 /* Check initial buttons */
327 bool displayPrev = false;
328 bool displayNext = false;
331 if (order == ItemOrder_Descending)
334 md.firstItem = extraItem;
335 } else if (order == ItemOrder_Ascending) {
337 md.lastItem = extraItem;
342 * If we're paginated, we have to find if there is another page.
343 * Sadly, the only way to do this is to try drawing more items!
345 if (pgn != MENU_NO_PAGINATION)
347 unsigned int lastItem = 0;
349 /* Find the last feasible item to search from. */
350 if (order == ItemOrder_Descending)
352 lastItem = drawItems[0].position;
353 if (lastItem >= totalItems - 1)
357 while (++lastItem < totalItems)
359 if (menu->GetItemInfo(lastItem, &dr) != NULL)
361 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
362 if (IsSlotItem(display, dr.style))
365 md.lastItem = lastItem;
370 } else if (order == ItemOrder_Ascending) {
371 lastItem = drawItems[0].position;
377 while (lastItem != 0)
379 if (menu->GetItemInfo(lastItem, &dr) != NULL)
381 mh->OnMenuDrawItem(menu, client, lastItem, dr.style);
382 if (IsSlotItem(display, dr.style))
385 md.firstItem = lastItem;
395 /* Draw the item according to the order */
396 menu_slots_t *slots = md.slots;
397 unsigned int position = 0; /* Keep track of the last position */
398 if (order == ItemOrder_Ascending)
400 for (unsigned int i=0; i<foundItems; i++)
402 ItemDrawInfo &dr = drawItems[i].draw;
403 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
405 position = display->DrawItem(dr);
409 slots[position].item = drawItems[i].position;
410 slots[position].type = ItemSel_Item;
413 } else if (order == ItemOrder_Descending) {
414 unsigned int i = foundItems;
415 /* NOTE: There will always be at least one item because
416 * of the check earlier.
420 ItemDrawInfo &dr = drawItems[i].draw;
421 if ((position = mh->OnMenuDisplayItem(menu, client, display, drawItems[i].position, dr)) == 0)
423 position = display->DrawItem(dr);
427 slots[position].item = drawItems[i].position;
428 slots[position].type = ItemSel_Item;
433 /* Now, we need to check if we need to add anything extra */
434 if (pgn != MENU_NO_PAGINATION)
436 bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL);
437 bool exitButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT;
438 bool exitBackButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXITBACK;
441 /* Calculate how many items we are allowed for control stuff */
442 unsigned int padding = style->GetMaxPageItems() - maxItems;
444 /* Add the number of available slots */
445 padding += (maxItems - foundItems);
447 /* Someday, if we are able to re-enable this, we will be very lucky men. */
449 if (!style->FeatureExists(MenuStyleFeature_ImplicitExit))
452 /* Even if we don't draw an exit button, we invalidate the slot. */
456 /* Otherwise, we don't draw anything and leave the slot available */
461 /* Subtract two slots for the displayNext/displayPrev padding */
464 /* If we have an "Exit Back" button and the space to draw it, do so. */
471 exitBackButton = false;
476 * We allow next/prev to be undrawn if neither exists.
477 * Thus, we only need padding if one of them will be drawn,
478 * or the exit button will be drawn.
480 ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER);
481 if (exitButton || (displayNext || displayPrev))
483 /* If there are no control options,
484 * Instead just pad with invisible slots.
486 if (!displayPrev && !displayPrev)
488 padItem.style = ITEMDRAW_NOTEXT;
490 /* Add spacers so we can pad to the end */
491 for (unsigned int i=0; i<padding; i++)
493 position = display->DrawItem(padItem);
494 slots[position].type = ItemSel_None;
498 /* Put a fake spacer before control stuff, if possible */
499 if ((displayPrev || displayNext) || exitButton)
501 ItemDrawInfo draw("", ITEMDRAW_RAWLINE|ITEMDRAW_SPACER);
502 display->DrawItem(draw);
505 ItemDrawInfo dr(text, 0);
507 * If we have one or the other, we need to have spacers for both.
509 if (displayPrev || displayNext)
512 ItemDrawInfo padCtrlItem(NULL, ITEMDRAW_SPACER|ITEMDRAW_CONTROL);
513 if (displayPrev || canDrawDisabled)
517 CorePlayerTranslate(client, text, sizeof(text), "Back", NULL);
518 dr.style = ITEMDRAW_CONTROL;
519 position = display->DrawItem(dr);
520 slots[position].type = ItemSel_ExitBack;
522 CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL);
523 dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
524 position = display->DrawItem(dr);
525 slots[position].type = ItemSel_Back;
527 } else if (displayNext || exitButton) {
528 /* If we can't display this, and there is an exit button,
531 position = display->DrawItem(padCtrlItem);
532 slots[position].type = ItemSel_None;
536 if (displayNext || canDrawDisabled)
538 CorePlayerTranslate(client, text, sizeof(text), "Next", NULL);
539 dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL;
540 position = display->DrawItem(dr);
541 slots[position].type = ItemSel_Next;
542 } else if (exitButton) {
543 /* If we can't display this,
544 * but there is an "exit" button, we need to pad!
546 position = display->DrawItem(padCtrlItem);
547 slots[position].type = ItemSel_None;
550 /* Otherwise, bump to two slots! */
551 ItemDrawInfo numBump(NULL, ITEMDRAW_NOTEXT);
552 position = display->DrawItem(numBump);
553 slots[position].type = ItemSel_None;
554 position = display->DrawItem(numBump);
555 slots[position].type = ItemSel_None;
561 CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL);
562 dr.style = ITEMDRAW_CONTROL;
563 position = display->DrawItem(dr);
564 slots[position].type = ItemSel_Exit;
568 /* Lastly, fill in any slots we could have missed */
569 for (unsigned int i = position + 1; i < 10; i++)
571 slots[i].type = ItemSel_None;
575 mh->OnMenuDisplay(menu, client, display);
576 display->DrawTitle(menu->GetDefaultTitle(), true);
581 IMenuStyle *MenuManager::GetDefaultStyle()
583 return m_pDefaultStyle;
586 bool MenuManager::MenuSoundsEnabled()
588 return (sm_menu_sounds.GetInt() != 0);
591 ConfigResult MenuManager::OnSourceModConfigChanged(const char *key,
597 if (strcmp(key, "MenuItemSound") == 0)
599 m_SelectSound.assign(value);
600 return ConfigResult_Accept;
601 } else if (strcmp(key, "MenuExitBackSound") == 0) {
602 m_ExitBackSound.assign(value);
603 return ConfigResult_Accept;
604 } else if (strcmp(key, "MenuExitSound") == 0) {
605 m_ExitSound.assign(value);
606 return ConfigResult_Accept;
609 return ConfigResult_Ignore;
612 const char *MenuManager::GetMenuSound(ItemSelection sel)
614 const char *sound = NULL;
622 if (m_SelectSound.size() > 0)
624 sound = m_SelectSound.c_str();
628 case ItemSel_ExitBack:
630 if (m_ExitBackSound.size() > 0)
632 sound = m_ExitBackSound.c_str();
638 if (m_ExitSound.size() > 0)
640 sound = m_ExitSound.c_str();
653 void MenuManager::OnSourceModLevelChange(const char *mapName)
655 if (m_SelectSound.size() > 0)
657 enginesound->PrecacheSound(m_SelectSound.c_str(), true);
659 if (m_ExitBackSound.size() > 0)
661 enginesound->PrecacheSound(m_ExitBackSound.c_str(), true);
663 if (m_ExitSound.size() > 0)
665 enginesound->PrecacheSound(m_ExitSound.c_str(), true);
669 void MenuManager::CancelMenu(IBaseMenu *menu)
671 if (s_VoteHandler.GetCurrentMenu() == menu
672 && !s_VoteHandler.IsCancelling())
674 s_VoteHandler.CancelVoting();
681 bool MenuManager::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags)
683 return s_VoteHandler.StartVote(menu, num_clients, clients, max_time, flags);
686 bool MenuManager::IsVoteInProgress()
688 return s_VoteHandler.IsVoteInProgress();
691 void MenuManager::CancelVoting()
693 s_VoteHandler.CancelVoting();
696 unsigned int MenuManager::GetRemainingVoteDelay()
698 return s_VoteHandler.GetRemainingVoteDelay();