Updated license headers on virtually all files with extremely minor changes in the name of some sort of strange consistency.
All plugin and include file headers also have been changed to say about GPL3 instead of GPL2.
(This day shall henceforth be known as the Eighty Column Massacre of '07)
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();