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
16 #include "sm_stringutil.h"
17 #include "MenuStyle_Base.h"
18 #include "PlayerManager.h"
19 #include "MenuManager.h"
20 #include "HandleSys.h"
21 #include "CellRecipientFilter.h"
22 #if defined MENU_DEBUG
26 BaseMenuStyle::BaseMenuStyle() : m_WatchList(256), m_hHandle(BAD_HANDLE)
30 Handle_t BaseMenuStyle::GetHandle()
32 /* Don't create the handle until we need it */
33 if (m_hHandle == BAD_HANDLE)
35 m_hHandle = g_Menus.CreateStyleHandle(this);
41 void BaseMenuStyle::AddClientToWatch(int client)
43 #if defined MENU_DEBUG
44 g_Logger.LogMessage("[SM_MENU] AddClientToWatch(%d)", client);
46 m_WatchList.push_back(client);
49 void BaseMenuStyle::RemoveClientFromWatch(int client)
51 #if defined MENU_DEBUG
52 g_Logger.LogMessage("[SM_MENU] RemoveClientFromWatch(%d)", client);
54 m_WatchList.remove(client);
57 void BaseMenuStyle::_CancelClientMenu(int client, MenuCancelReason reason, bool bAutoIgnore/* =false */)
59 #if defined MENU_DEBUG
60 g_Logger.LogMessage("[SM_MENU] _CancelClientMenu() (client %d) (bAutoIgnore %d) (reason %d)", client, bAutoIgnore, reason);
62 CBaseMenuPlayer *player = GetMenuPlayer(client);
63 menu_states_t &states = player->states;
65 bool bOldIgnore = player->bAutoIgnore;
68 player->bAutoIgnore = true;
72 IMenuHandler *mh = states.mh;
73 IBaseMenu *menu = states.menu;
76 player->bInMenu = false;
77 if (player->menuHoldTime)
79 RemoveClientFromWatch(client);
83 mh->OnMenuCancel(menu, client, reason);
85 /* Only fire end if there's a valid menu */
88 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
93 player->bAutoIgnore = bOldIgnore;
97 void BaseMenuStyle::CancelMenu(CBaseMenu *menu)
99 #if defined MENU_DEBUG
100 g_Logger.LogMessage("[SM_MENU] CancelMenu() (menu %p)", menu);
102 int maxClients = g_Players.GetMaxClients();
103 for (int i=1; i<=maxClients; i++)
105 CBaseMenuPlayer *player = GetMenuPlayer(i);
108 menu_states_t &states = player->states;
109 if (states.menu == menu)
111 _CancelClientMenu(i, MenuCancel_Interrupted);
117 bool BaseMenuStyle::CancelClientMenu(int client, bool autoIgnore)
119 #if defined MENU_DEBUG
120 g_Logger.LogMessage("[SM_MENU] CancelClientMenu() (client %d) (bAutoIgnore %d)", client, autoIgnore);
122 if (client < 1 || client > g_Players.MaxClients())
127 CBaseMenuPlayer *player = GetMenuPlayer(client);
128 if (!player->bInMenu)
133 _CancelClientMenu(client, MenuCancel_Interrupted, autoIgnore);
138 MenuSource BaseMenuStyle::GetClientMenu(int client, void **object)
140 if (client < 1 || client > g_Players.GetMaxClients())
142 return MenuSource_None;
145 CBaseMenuPlayer *player = GetMenuPlayer(client);
150 if ((menu=player->states.menu) != NULL)
156 return MenuSource_BaseMenu;
159 return MenuSource_Display;
160 } else if (player->bInExternMenu) {
161 if (player->menuHoldTime != 0
162 && (gpGlobals->curtime > player->menuStartTime + player->menuHoldTime))
164 player->bInExternMenu = false;
165 return MenuSource_None;
167 return MenuSource_External;
170 return MenuSource_None;
173 void BaseMenuStyle::OnClientDisconnected(int client)
175 #if defined MENU_DEBUG
176 g_Logger.LogMessage("[SM_MENU] OnClientDisconnected(%d)", client);
178 CBaseMenuPlayer *player = GetMenuPlayer(client);
179 if (!player->bInMenu)
184 _CancelClientMenu(client, MenuCancel_Disconnected, true);
186 player->bInMenu = false;
187 player->bInExternMenu = false;
190 static int do_lookup[256];
191 void BaseMenuStyle::ProcessWatchList()
193 if (!m_WatchList.size())
198 #if defined MENU_DEBUG
199 g_Logger.LogMessage("BaseMenuStyle::ProcessWatchList(%d,%d,%d,%d,%d,%p)",
201 m_WatchList.m_FirstLink,
202 m_WatchList.m_FreeNodes,
203 m_WatchList.m_LastLink,
204 m_WatchList.m_MaxSize,
205 m_WatchList.m_Nodes);
208 unsigned int total = 0;
209 for (FastLink<int>::iterator iter=m_WatchList.begin(); iter!=m_WatchList.end(); ++iter)
211 do_lookup[total++] = (*iter);
214 #if defined MENU_DEBUG
217 g_Logger.LogMessage("[SM_MENU] ProcessWatchList() found %d clients", total);
222 CBaseMenuPlayer *player;
223 float curTime = gpGlobals->curtime;
224 for (unsigned int i=0; i<total; i++)
226 client = do_lookup[i];
227 player = GetMenuPlayer(client);
228 #if defined MENU_DEBUG
229 g_Logger.LogMessage("[SM_MENU] ProcessWatchList() (client %d) (bInMenu %d) (menuHoldTime %d) (curTime %f) (menuStartTime %f)",
232 player->menuHoldTime,
234 player->menuStartTime);
236 if (!player->bInMenu || !player->menuHoldTime)
238 #if defined MENU_DEBUG
239 g_Logger.LogMessage("[SM_MENU] ProcessWatchList() removing client %d", client);
241 m_WatchList.remove(client);
244 if (curTime > player->menuStartTime + player->menuHoldTime)
246 _CancelClientMenu(client, MenuCancel_Timeout, false);
251 void BaseMenuStyle::ClientPressedKey(int client, unsigned int key_press)
253 #if defined MENU_DEBUG
254 g_Logger.LogMessage("[SM_MENU] ClientPressedKey() (client %d) (key_press %d)", client, key_press);
256 CBaseMenuPlayer *player = GetMenuPlayer(client);
258 /* First question: Are we in a menu? */
259 if (!player->bInMenu)
265 unsigned int item = 0;
266 MenuCancelReason reason = MenuCancel_Exit;
267 MenuEndReason end_reason = MenuEnd_Selected;
268 menu_states_t &states = player->states;
270 assert(states.mh != NULL);
272 if (states.menu == NULL)
275 } else if (key_press < 1 || key_press > GetMaxPageItems()) {
278 ItemSelection type = states.slots[key_press].type;
280 /* Check if we should play a sound about the type */
281 if (g_Menus.MenuSoundsEnabled())
283 CellRecipientFilter filter;
287 filter.Initialize(clients, 1);
289 const char *sound = g_Menus.GetMenuSound(type);
293 edict_t *pEdict = engine->PEntityOfEntIndex(client);
296 ICollideable *pCollideable = pEdict->GetCollideable();
300 const Vector & pos = pCollideable->GetCollisionOrigin();
302 enginesound->EmitSound(filter,
316 /* For navigational items, we're going to redisplay */
317 if (type == ItemSel_Back)
319 if (!RedoClientMenu(client, ItemOrder_Descending))
322 reason = MenuCancel_NoDisplay;
323 end_reason = MenuEnd_Cancelled;
327 } else if (type == ItemSel_Next) {
328 if (!RedoClientMenu(client, ItemOrder_Ascending))
330 cancel = true; /* I like Saltines. */
331 reason = MenuCancel_NoDisplay;
332 end_reason = MenuEnd_Cancelled;
336 } else if (type == ItemSel_Exit || type == ItemSel_None) {
338 reason = MenuCancel_Exit;
339 end_reason = MenuEnd_Exit;
340 } else if (type == ItemSel_ExitBack) {
342 reason = MenuCancel_ExitBack;
343 end_reason = MenuEnd_ExitBack;
345 item = states.slots[key_press].item;
350 IMenuHandler *mh = states.mh;
351 IBaseMenu *menu = states.menu;
354 player->bInMenu = false;
355 if (player->menuHoldTime)
357 RemoveClientFromWatch(client);
362 mh->OnMenuCancel(menu, client, reason);
364 mh->OnMenuSelect(menu, client, item);
367 /* Only fire end for valid menus */
370 mh->OnMenuEnd(menu, end_reason);
374 bool BaseMenuStyle::DoClientMenu(int client, IMenuPanel *menu, IMenuHandler *mh, unsigned int time)
376 #if defined MENU_DEBUG
377 g_Logger.LogMessage("[SM_MENU] DoClientMenu() (client %d) (panel %p) (mh %p) (time %d)",
383 CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
384 if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
389 CBaseMenuPlayer *player = GetMenuPlayer(client);
390 if (player->bAutoIgnore)
395 /* For the duration of this, we are going to totally ignore whether
396 * the player is already in a menu or not (except to cancel the old one).
397 * Instead, we are simply going to ignore any further menu displays, so
398 * this display can't be interrupted.
400 player->bAutoIgnore = true;
402 /* Cancel any old menus */
403 menu_states_t &states = player->states;
406 _CancelClientMenu(client, MenuCancel_Interrupted, true);
409 states.firstItem = 0;
413 states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
414 player->bInMenu = true;
415 player->bInExternMenu = false;
416 player->menuStartTime = gpGlobals->curtime;
417 player->menuHoldTime = time;
421 AddClientToWatch(client);
424 /* Draw the display */
425 SendDisplay(client, menu);
427 /* We can be interrupted again! */
428 player->bAutoIgnore = false;
433 bool BaseMenuStyle::DoClientMenu(int client, CBaseMenu *menu, IMenuHandler *mh, unsigned int time)
435 #if defined MENU_DEBUG
436 g_Logger.LogMessage("[SM_MENU] DoClientMenu() (client %d) (menu %p) (mh %p) (time %d)",
442 mh->OnMenuStart(menu);
444 CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
445 if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
447 #if defined MENU_DEBUG
448 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Failed to display to client %d", client);
450 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
451 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
455 CBaseMenuPlayer *player = GetMenuPlayer(client);
456 if (player->bAutoIgnore)
458 #if defined MENU_DEBUG
459 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Client %d is autoIgnoring", client);
461 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
462 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
466 /* For the duration of this, we are going to totally ignore whether
467 * the player is already in a menu or not (except to cancel the old one).
468 * Instead, we are simply going to ignore any further menu displays, so
469 * this display can't be interrupted.
471 player->bAutoIgnore = true;
473 /* Cancel any old menus */
474 menu_states_t &states = player->states;
477 #if defined MENU_DEBUG
478 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Cancelling old menu to client %d", client);
480 _CancelClientMenu(client, MenuCancel_Interrupted, true);
483 states.firstItem = 0;
487 states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
489 IMenuPanel *display = g_Menus.RenderMenu(client, states, ItemOrder_Ascending);
492 #if defined MENU_DEBUG
493 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Failed to render to client %d", client);
495 player->bAutoIgnore = false;
496 player->bInMenu = false;
497 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
498 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
502 /* Finally, set our states */
503 player->bInMenu = true;
504 player->bInExternMenu = false;
505 player->menuStartTime = gpGlobals->curtime;
506 player->menuHoldTime = time;
510 AddClientToWatch(client);
513 /* Draw the display */
514 SendDisplay(client, display);
516 /* Free the display pointer */
517 display->DeleteThis();
519 /* We can be interrupted again! */
520 player->bAutoIgnore = false;
522 #if defined MENU_DEBUG
523 g_Logger.LogMessage("[SM_MENU] DoClientMenu() finished successfully (client %d)", client);
529 bool BaseMenuStyle::RedoClientMenu(int client, ItemOrder order)
531 #if defined MENU_DEBUG
532 g_Logger.LogMessage("[SM_MENU] RedoClientMenu() (client %d) (order %d)", client, order);
534 CBaseMenuPlayer *player = GetMenuPlayer(client);
535 menu_states_t &states = player->states;
537 player->bAutoIgnore = true;
538 IMenuPanel *display = g_Menus.RenderMenu(client, states, order);
541 #if defined MENU_DEBUG
542 g_Logger.LogMessage("[SM_MENU] RedoClientMenu(): Failed to render menu");
544 if (player->menuHoldTime)
546 RemoveClientFromWatch(client);
548 player->bAutoIgnore = false;
552 SendDisplay(client, display);
554 display->DeleteThis();
556 player->bAutoIgnore = false;
558 #if defined MENU_DEBUG
559 g_Logger.LogMessage("[SM_MENU] RedoClientMenu(): Succeeded to client %d", client);
565 CBaseMenu::CBaseMenu(IMenuHandler *pHandler, IMenuStyle *pStyle, IdentityToken_t *pOwner) :
566 m_pStyle(pStyle), m_Strings(512), m_Pagination(7), m_ExitButton(true),
567 m_bShouldDelete(false), m_bCancelling(false), m_pOwner(pOwner ? pOwner : g_pCoreIdent),
568 m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler),
569 m_pVoteHandler(NULL), m_ExitBackButton(false)
573 CBaseMenu::~CBaseMenu()
575 g_Menus.ReleaseVoteWrapper(m_pVoteHandler);
578 Handle_t CBaseMenu::GetHandle()
582 m_hHandle = g_Menus.CreateMenuHandle(this, m_pOwner);
588 bool CBaseMenu::AppendItem(const char *info, const ItemDrawInfo &draw)
590 if (m_Pagination == (unsigned)MENU_NO_PAGINATION
591 && m_items.size() >= m_pStyle->GetMaxPageItems())
598 item.infoString = m_Strings.AddString(info);
601 item.displayString = m_Strings.AddString(draw.display);
603 item.style = draw.style;
604 item.access = draw.access;
607 m_items.push_back(item);
612 bool CBaseMenu::InsertItem(unsigned int position, const char *info, const ItemDrawInfo &draw)
614 if (m_Pagination == (unsigned)MENU_NO_PAGINATION
615 && m_items.size() >= m_pStyle->GetMaxPageItems())
620 if (position >= m_items.size())
626 item.infoString = m_Strings.AddString(info);
629 item.displayString = m_Strings.AddString(draw.display);
631 item.style = draw.style;
632 item.access = draw.access;
634 CVector<CItem>::iterator iter = m_items.iterAt(position);
635 m_items.insert(iter, item);
640 bool CBaseMenu::RemoveItem(unsigned int position)
642 if (position >= m_items.size())
647 m_items.erase(m_items.iterAt(position));
649 if (m_items.size() == 0)
657 void CBaseMenu::RemoveAllItems()
663 const char *CBaseMenu::GetItemInfo(unsigned int position, ItemDrawInfo *draw/* =NULL */)
665 if (position >= m_items.size())
672 draw->display = m_Strings.GetString(m_items[position].displayString);
673 draw->style = m_items[position].style;
676 return m_Strings.GetString(m_items[position].infoString);
679 unsigned int CBaseMenu::GetItemCount()
681 return m_items.size();
684 bool CBaseMenu::SetPagination(unsigned int itemsPerPage)
686 if (itemsPerPage > 7 || itemsPerPage == 1)
691 m_Pagination = itemsPerPage;
696 unsigned int CBaseMenu::GetPagination()
701 IMenuStyle *CBaseMenu::GetDrawStyle()
706 void CBaseMenu::SetDefaultTitle(const char *message)
708 m_Title.assign(message);
711 const char *CBaseMenu::GetDefaultTitle()
713 return m_Title.c_str();
716 bool CBaseMenu::GetExitButton()
721 bool CBaseMenu::SetExitButton(bool set)
727 void CBaseMenu::Cancel()
734 #if defined MENU_DEBUG
735 g_Logger.LogMessage("[SM_MENU] CBaseMenu::Cancel(%p) (m_pVote %p) (inVote %d) (m_bShouldDelete %d)",
738 m_pVoteHandler ? m_pVoteHandler->IsVoteInProgress() : false,
742 if (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress())
744 m_pVoteHandler->CancelVoting();
747 m_bCancelling = true;
749 m_bCancelling = false;
757 void CBaseMenu::Destroy(bool releaseHandle)
759 /* Check if we shouldn't be here */
765 #if defined MENU_DEBUG
766 g_Logger.LogMessage("[SM_MENU] CBaseMenu::Destroy(%p) (release %d) (m_bCancelling %d) (m_bShouldDelete %d)",
773 /* Save the destruction hint about our handle */
774 m_bWillFreeHandle = releaseHandle;
776 /* Now actually do stuff */
777 if (!m_bCancelling || m_bShouldDelete)
782 m_bShouldDelete = true;
786 void CBaseMenu::InternalDelete()
788 if (m_bWillFreeHandle && m_hHandle != BAD_HANDLE)
790 Handle_t hndl = m_hHandle;
793 sec.pOwner = m_pOwner;
794 sec.pIdentity = g_pCoreIdent;
796 m_hHandle = BAD_HANDLE;
798 g_HandleSys.FreeHandle(hndl, &sec);
801 m_pHandler->OnMenuDestroy(this);
806 bool CBaseMenu::BroadcastVote(int clients[],
807 unsigned int numClients,
808 unsigned int maxTime,
811 #if defined MENU_DEBUG
812 g_Logger.LogMessage("[SM_MENU] CBaseMenu::BroadcastVote(%p) (maxTime %d) (numClients %d) (m_pVote %p) (inVote %d)",
817 m_pVoteHandler ? m_pVoteHandler->IsVoteInProgress() : false);
821 m_pVoteHandler = g_Menus.CreateVoteWrapper(m_pHandler);
822 } else if (m_pVoteHandler->IsVoteInProgress()) {
826 m_pVoteHandler->InitializeVoting(this);
828 for (unsigned int i=0; i<numClients; i++)
830 VoteDisplay(clients[i], maxTime);
833 m_pVoteHandler->StartVoting();
838 bool CBaseMenu::IsVoteInProgress()
840 return (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress());
843 bool CBaseMenu::GetExitBackButton()
845 return m_ExitBackButton;
848 void CBaseMenu::SetExitBackButton(bool set)
850 m_ExitBackButton = set;