fixed a serious corruption bug in fastlink causing menus to corrupt memory after a while
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 #if defined MENU_DEBUG
25 BaseMenuStyle::BaseMenuStyle() : m_WatchList(256), m_hHandle(BAD_HANDLE)
29 Handle_t BaseMenuStyle::GetHandle()
31 /* Don't create the handle until we need it */
32 if (m_hHandle == BAD_HANDLE)
34 m_hHandle = g_Menus.CreateStyleHandle(this);
40 void BaseMenuStyle::AddClientToWatch(int client)
42 #if defined MENU_DEBUG
43 g_Logger.LogMessage("[SM_MENU] AddClientToWatch(%d)", client);
45 m_WatchList.push_back(client);
48 void BaseMenuStyle::RemoveClientFromWatch(int client)
50 #if defined MENU_DEBUG
51 g_Logger.LogMessage("[SM_MENU] RemoveClientFromWatch(%d)", client);
53 m_WatchList.remove(client);
56 void BaseMenuStyle::_CancelClientMenu(int client, MenuCancelReason reason, bool bAutoIgnore/* =false */)
58 #if defined MENU_DEBUG
59 g_Logger.LogMessage("[SM_MENU] _CancelClientMenu() (client %d) (bAutoIgnore %d) (reason %d)", client, bAutoIgnore, reason);
61 CBaseMenuPlayer *player = GetMenuPlayer(client);
62 menu_states_t &states = player->states;
64 bool bOldIgnore = player->bAutoIgnore;
67 player->bAutoIgnore = true;
71 IMenuHandler *mh = states.mh;
72 IBaseMenu *menu = states.menu;
75 player->bInMenu = false;
76 if (player->menuHoldTime)
78 RemoveClientFromWatch(client);
82 mh->OnMenuCancel(menu, client, reason);
84 /* Only fire end if there's a valid menu */
87 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
92 player->bAutoIgnore = bOldIgnore;
96 void BaseMenuStyle::CancelMenu(CBaseMenu *menu)
98 #if defined MENU_DEBUG
99 g_Logger.LogMessage("[SM_MENU] CancelMenu() (menu %p)", menu);
101 int maxClients = g_Players.GetMaxClients();
102 for (int i=1; i<=maxClients; i++)
104 CBaseMenuPlayer *player = GetMenuPlayer(i);
107 menu_states_t &states = player->states;
108 if (states.menu == menu)
110 _CancelClientMenu(i, MenuCancel_Interrupted);
116 bool BaseMenuStyle::CancelClientMenu(int client, bool autoIgnore)
118 #if defined MENU_DEBUG
119 g_Logger.LogMessage("[SM_MENU] CancelClientMenu() (client %d) (bAutoIgnore %d)", client, autoIgnore);
121 if (client < 1 || client > g_Players.MaxClients())
126 CBaseMenuPlayer *player = GetMenuPlayer(client);
127 if (!player->bInMenu)
132 _CancelClientMenu(client, MenuCancel_Interrupted, autoIgnore);
137 MenuSource BaseMenuStyle::GetClientMenu(int client, void **object)
139 if (client < 1 || client > g_Players.GetMaxClients())
141 return MenuSource_None;
144 CBaseMenuPlayer *player = GetMenuPlayer(client);
149 if ((menu=player->states.menu) != NULL)
155 return MenuSource_BaseMenu;
158 return MenuSource_Display;
159 } else if (player->bInExternMenu) {
160 if (player->menuHoldTime != 0
161 && (gpGlobals->curtime > player->menuStartTime + player->menuHoldTime))
163 player->bInExternMenu = false;
164 return MenuSource_None;
166 return MenuSource_External;
169 return MenuSource_None;
172 void BaseMenuStyle::OnClientDisconnected(int client)
174 #if defined MENU_DEBUG
175 g_Logger.LogMessage("[SM_MENU] OnClientDisconnected(%d)", client);
177 CBaseMenuPlayer *player = GetMenuPlayer(client);
178 if (!player->bInMenu)
183 _CancelClientMenu(client, MenuCancel_Disconnected, true);
185 player->bInMenu = false;
186 player->bInExternMenu = false;
189 static int do_lookup[256];
190 void BaseMenuStyle::ProcessWatchList()
192 if (!m_WatchList.size())
197 #if defined MENU_DEBUG
198 g_Logger.LogMessage("BaseMenuStyle::ProcessWatchList(%d,%d,%d,%d,%d,%p)",
200 m_WatchList.m_FirstLink,
201 m_WatchList.m_FreeNodes,
202 m_WatchList.m_LastLink,
203 m_WatchList.m_MaxSize,
204 m_WatchList.m_Nodes);
207 unsigned int total = 0;
208 for (FastLink<int>::iterator iter=m_WatchList.begin(); iter!=m_WatchList.end(); ++iter)
210 do_lookup[total++] = (*iter);
213 #if defined MENU_DEBUG
216 g_Logger.LogMessage("[SM_MENU] ProcessWatchList() found %d clients", total);
221 CBaseMenuPlayer *player;
222 float curTime = gpGlobals->curtime;
223 for (unsigned int i=0; i<total; i++)
225 client = do_lookup[i];
226 player = GetMenuPlayer(client);
227 #if defined MENU_DEBUG
228 g_Logger.LogMessage("[SM_MENU] ProcessWatchList() (client %d) (bInMenu %d) (menuHoldTime %d) (curTime %f) (menuStartTime %f)",
231 player->menuHoldTime,
233 player->menuStartTime);
235 if (!player->bInMenu || !player->menuHoldTime)
237 #if defined MENU_DEBUG
238 g_Logger.LogMessage("[SM_MENU] ProcessWatchList() removing client %d", client);
240 m_WatchList.remove(client);
243 if (curTime > player->menuStartTime + player->menuHoldTime)
245 _CancelClientMenu(client, MenuCancel_Timeout, false);
250 void BaseMenuStyle::ClientPressedKey(int client, unsigned int key_press)
252 #if defined MENU_DEBUG
253 g_Logger.LogMessage("[SM_MENU] ClientPressedKey() (client %d) (key_press %d)", client, key_press);
255 CBaseMenuPlayer *player = GetMenuPlayer(client);
257 /* First question: Are we in a menu? */
258 if (!player->bInMenu)
264 unsigned int item = 0;
265 MenuCancelReason reason = MenuCancel_Exit;
266 MenuEndReason end_reason = MenuEnd_Selected;
267 menu_states_t &states = player->states;
269 assert(states.mh != NULL);
271 if (states.menu == NULL)
274 } else if (key_press < 1 || key_press > GetMaxPageItems()) {
277 ItemSelection type = states.slots[key_press].type;
279 /* For navigational items, we're going to redisplay */
280 if (type == ItemSel_Back)
282 if (!RedoClientMenu(client, ItemOrder_Descending))
285 reason = MenuCancel_NoDisplay;
286 end_reason = MenuEnd_Cancelled;
290 } else if (type == ItemSel_Next) {
291 if (!RedoClientMenu(client, ItemOrder_Ascending))
293 cancel = true; /* I like Saltines. */
294 reason = MenuCancel_NoDisplay;
295 end_reason = MenuEnd_Cancelled;
299 } else if (type == ItemSel_Exit || type == ItemSel_None) {
301 reason = MenuCancel_Exit;
302 end_reason = MenuEnd_Exit;
303 } else if (type == ItemSel_ExitBack) {
305 reason = MenuCancel_ExitBack;
306 end_reason = MenuEnd_ExitBack;
308 item = states.slots[key_press].item;
313 IMenuHandler *mh = states.mh;
314 IBaseMenu *menu = states.menu;
317 player->bInMenu = false;
318 if (player->menuHoldTime)
320 RemoveClientFromWatch(client);
325 mh->OnMenuCancel(menu, client, reason);
327 mh->OnMenuSelect(menu, client, item);
330 /* Only fire end for valid menus */
333 mh->OnMenuEnd(menu, end_reason);
337 bool BaseMenuStyle::DoClientMenu(int client, IMenuPanel *menu, IMenuHandler *mh, unsigned int time)
339 #if defined MENU_DEBUG
340 g_Logger.LogMessage("[SM_MENU] DoClientMenu() (client %d) (panel %p) (mh %p) (time %d)",
346 CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
347 if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
352 CBaseMenuPlayer *player = GetMenuPlayer(client);
353 if (player->bAutoIgnore)
358 /* For the duration of this, we are going to totally ignore whether
359 * the player is already in a menu or not (except to cancel the old one).
360 * Instead, we are simply going to ignore any further menu displays, so
361 * this display can't be interrupted.
363 player->bAutoIgnore = true;
365 /* Cancel any old menus */
366 menu_states_t &states = player->states;
369 _CancelClientMenu(client, MenuCancel_Interrupted, true);
372 states.firstItem = 0;
376 states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
377 player->bInMenu = true;
378 player->bInExternMenu = false;
379 player->menuStartTime = gpGlobals->curtime;
380 player->menuHoldTime = time;
384 AddClientToWatch(client);
387 /* Draw the display */
388 SendDisplay(client, menu);
390 /* We can be interrupted again! */
391 player->bAutoIgnore = false;
396 bool BaseMenuStyle::DoClientMenu(int client, CBaseMenu *menu, IMenuHandler *mh, unsigned int time)
398 #if defined MENU_DEBUG
399 g_Logger.LogMessage("[SM_MENU] DoClientMenu() (client %d) (menu %p) (mh %p) (time %d)",
405 mh->OnMenuStart(menu);
407 CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
408 if (!pPlayer || pPlayer->IsFakeClient() || !pPlayer->IsInGame())
410 #if defined MENU_DEBUG
411 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Failed to display to client %d", client);
413 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
414 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
418 CBaseMenuPlayer *player = GetMenuPlayer(client);
419 if (player->bAutoIgnore)
421 #if defined MENU_DEBUG
422 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Client %d is autoIgnoring", client);
424 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
425 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
429 /* For the duration of this, we are going to totally ignore whether
430 * the player is already in a menu or not (except to cancel the old one).
431 * Instead, we are simply going to ignore any further menu displays, so
432 * this display can't be interrupted.
434 player->bAutoIgnore = true;
436 /* Cancel any old menus */
437 menu_states_t &states = player->states;
440 #if defined MENU_DEBUG
441 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Cancelling old menu to client %d", client);
443 _CancelClientMenu(client, MenuCancel_Interrupted, true);
446 states.firstItem = 0;
450 states.apiVers = SMINTERFACE_MENUMANAGER_VERSION;
452 IMenuPanel *display = g_Menus.RenderMenu(client, states, ItemOrder_Ascending);
455 #if defined MENU_DEBUG
456 g_Logger.LogMessage("[SM_MENU] DoClientMenu(): Failed to render to client %d", client);
458 player->bAutoIgnore = false;
459 player->bInMenu = false;
460 mh->OnMenuCancel(menu, client, MenuCancel_NoDisplay);
461 mh->OnMenuEnd(menu, MenuEnd_Cancelled);
465 /* Finally, set our states */
466 player->bInMenu = true;
467 player->bInExternMenu = false;
468 player->menuStartTime = gpGlobals->curtime;
469 player->menuHoldTime = time;
473 AddClientToWatch(client);
476 /* Draw the display */
477 SendDisplay(client, display);
479 /* Free the display pointer */
480 display->DeleteThis();
482 /* We can be interrupted again! */
483 player->bAutoIgnore = false;
485 #if defined MENU_DEBUG
486 g_Logger.LogMessage("[SM_MENU] DoClientMenu() finished successfully (client %d)", client);
492 bool BaseMenuStyle::RedoClientMenu(int client, ItemOrder order)
494 #if defined MENU_DEBUG
495 g_Logger.LogMessage("[SM_MENU] RedoClientMenu() (client %d) (order %d)", client, order);
497 CBaseMenuPlayer *player = GetMenuPlayer(client);
498 menu_states_t &states = player->states;
500 player->bAutoIgnore = true;
501 IMenuPanel *display = g_Menus.RenderMenu(client, states, order);
504 #if defined MENU_DEBUG
505 g_Logger.LogMessage("[SM_MENU] RedoClientMenu(): Failed to render menu");
507 if (player->menuHoldTime)
509 RemoveClientFromWatch(client);
511 player->bAutoIgnore = false;
515 SendDisplay(client, display);
517 display->DeleteThis();
519 player->bAutoIgnore = false;
521 #if defined MENU_DEBUG
522 g_Logger.LogMessage("[SM_MENU] RedoClientMenu(): Succeeded to client %d", client);
528 CBaseMenu::CBaseMenu(IMenuHandler *pHandler, IMenuStyle *pStyle, IdentityToken_t *pOwner) :
529 m_pStyle(pStyle), m_Strings(512), m_Pagination(7), m_ExitButton(true),
530 m_bShouldDelete(false), m_bCancelling(false), m_pOwner(pOwner ? pOwner : g_pCoreIdent),
531 m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler),
532 m_pVoteHandler(NULL), m_ExitBackButton(false)
536 CBaseMenu::~CBaseMenu()
538 g_Menus.ReleaseVoteWrapper(m_pVoteHandler);
541 Handle_t CBaseMenu::GetHandle()
545 m_hHandle = g_Menus.CreateMenuHandle(this, m_pOwner);
551 bool CBaseMenu::AppendItem(const char *info, const ItemDrawInfo &draw)
553 if (m_Pagination == (unsigned)MENU_NO_PAGINATION
554 && m_items.size() >= m_pStyle->GetMaxPageItems())
561 item.infoString = m_Strings.AddString(info);
564 item.displayString = m_Strings.AddString(draw.display);
566 item.style = draw.style;
569 m_items.push_back(item);
574 bool CBaseMenu::InsertItem(unsigned int position, const char *info, const ItemDrawInfo &draw)
576 if (m_Pagination == (unsigned)MENU_NO_PAGINATION
577 && m_items.size() >= m_pStyle->GetMaxPageItems())
582 if (position >= m_items.size())
588 item.infoString = m_Strings.AddString(info);
591 item.displayString = m_Strings.AddString(draw.display);
593 item.style = draw.style;
595 CVector<CItem>::iterator iter = m_items.iterAt(position);
596 m_items.insert(iter, item);
601 bool CBaseMenu::RemoveItem(unsigned int position)
603 if (position >= m_items.size())
608 m_items.erase(m_items.iterAt(position));
610 if (m_items.size() == 0)
618 void CBaseMenu::RemoveAllItems()
624 const char *CBaseMenu::GetItemInfo(unsigned int position, ItemDrawInfo *draw/* =NULL */)
626 if (position >= m_items.size())
633 draw->display = m_Strings.GetString(m_items[position].displayString);
634 draw->style = m_items[position].style;
637 return m_Strings.GetString(m_items[position].infoString);
640 unsigned int CBaseMenu::GetItemCount()
642 return m_items.size();
645 bool CBaseMenu::SetPagination(unsigned int itemsPerPage)
647 if (itemsPerPage > 7 || itemsPerPage == 1)
652 m_Pagination = itemsPerPage;
657 unsigned int CBaseMenu::GetPagination()
662 IMenuStyle *CBaseMenu::GetDrawStyle()
667 void CBaseMenu::SetDefaultTitle(const char *message)
669 m_Title.assign(message);
672 const char *CBaseMenu::GetDefaultTitle()
674 return m_Title.c_str();
677 bool CBaseMenu::GetExitButton()
682 bool CBaseMenu::SetExitButton(bool set)
688 void CBaseMenu::Cancel()
695 #if defined MENU_DEBUG
696 g_Logger.LogMessage("[SM_MENU] CBaseMenu::Cancel(%p) (m_pVote %p) (inVote %d) (m_bShouldDelete %d)",
699 m_pVoteHandler ? m_pVoteHandler->IsVoteInProgress() : false,
703 if (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress())
705 m_pVoteHandler->CancelVoting();
708 m_bCancelling = true;
710 m_bCancelling = false;
718 void CBaseMenu::Destroy(bool releaseHandle)
720 /* Check if we shouldn't be here */
726 #if defined MENU_DEBUG
727 g_Logger.LogMessage("[SM_MENU] CBaseMenu::Destroy(%p) (release %d) (m_bCancelling %d) (m_bShouldDelete %d)",
734 /* Save the destruction hint about our handle */
735 m_bWillFreeHandle = releaseHandle;
737 /* Now actually do stuff */
738 if (!m_bCancelling || m_bShouldDelete)
743 m_bShouldDelete = true;
747 void CBaseMenu::InternalDelete()
749 if (m_bWillFreeHandle && m_hHandle != BAD_HANDLE)
751 Handle_t hndl = m_hHandle;
754 sec.pOwner = m_pOwner;
755 sec.pIdentity = g_pCoreIdent;
757 m_hHandle = BAD_HANDLE;
759 g_HandleSys.FreeHandle(hndl, &sec);
762 m_pHandler->OnMenuDestroy(this);
767 bool CBaseMenu::BroadcastVote(int clients[],
768 unsigned int numClients,
769 unsigned int maxTime,
772 #if defined MENU_DEBUG
773 g_Logger.LogMessage("[SM_MENU] CBaseMenu::BroadcastVote(%p) (maxTime %d) (numClients %d) (m_pVote %p) (inVote %d)",
778 m_pVoteHandler ? m_pVoteHandler->IsVoteInProgress() : false);
782 m_pVoteHandler = g_Menus.CreateVoteWrapper(m_pHandler);
783 } else if (m_pVoteHandler->IsVoteInProgress()) {
787 m_pVoteHandler->InitializeVoting(this);
789 for (unsigned int i=0; i<numClients; i++)
791 VoteDisplay(clients[i], maxTime);
794 m_pVoteHandler->StartVoting();
799 bool CBaseMenu::IsVoteInProgress()
801 return (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress());
804 bool CBaseMenu::GetExitBackButton()
806 return m_ExitBackButton;
809 void CBaseMenu::SetExitBackButton(bool set)
811 m_ExitBackButton = set;