diff --git a/core/MenuManager.cpp b/core/MenuManager.cpp index d7e50d5f..111d47ff 100644 --- a/core/MenuManager.cpp +++ b/core/MenuManager.cpp @@ -12,9 +12,9 @@ * Version: $Id$ */ - -#include #include +#include +#include #include "MenuManager.h" #include "sm_stringutil.h" #include "sourcemm_api.h" @@ -25,115 +25,188 @@ MenuManager g_Menus; -/************************************* - ************************************* - **** BROADCAST HANDLING WRAPPERS **** - ************************************* - *************************************/ +/******************************* + ******************************* + ******** VOTE HANDLER ********* + ******************************* + *******************************/ -BroadcastHandler::BroadcastHandler(IMenuHandler *handler) : m_pHandler(handler), numClients(0) -{ -} - -unsigned int BroadcastHandler::GetMenuAPIVersion2() +unsigned int VoteMenuHandler::GetMenuAPIVersion2() { return m_pHandler->GetMenuAPIVersion2(); } -void BroadcastHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) +bool VoteMenuHandler::IsVoteInProgress() +{ + return (m_pCurMenu != NULL); +} + +void VoteMenuHandler::InitializeVoting(IBaseMenu *menu) +{ + m_Items = menu->GetItemCount(); + + if (m_Votes.size() < (size_t)m_Items) + { + /* Only clear the items we need to... */ + size_t size = m_Votes.size(); + for (size_t i=0; iOnMenuStart(m_pCurMenu); +} + +void VoteMenuHandler::StartVoting() +{ + m_bStarted = true; + + m_pHandler->OnMenuVoteStart(m_pCurMenu); + + /* By now we know how many clients were set. + * If there are none, we should end IMMEDIATELY. + */ + if (m_Clients == 0) + { + EndVoting(); + } +} + +void VoteMenuHandler::DecrementPlayerCount() +{ + assert(m_Clients > 0); + + m_Clients--; + + if (m_bStarted && m_Clients == 0) + { + EndVoting(); + } +} + +void VoteMenuHandler::EndVoting() +{ + unsigned int chosen = 0; + + /* If we got zero votes, take a shortcut. */ + if (m_NumVotes == 0) + { + /* Pick a random item and then jump far, far away. */ + srand((unsigned int)(time(NULL))); + chosen = (unsigned int)rand() % static_cast(m_Votes.size()); + goto picked_item; + } + + /* We can't have more dups than this! + * This is the max number of players. + */ + unsigned int dup_array[256]; + unsigned int dup_count = 0; + + size_t highest = 0; + for (size_t i=1; i m_Votes[highest]) + { + /* If we have a new highest count, mark it and trash the duplicate + * list by setting the total to 0. + */ + highest = i; + dup_count = 0; + } else if (m_Votes[i] == m_Votes[highest]) { + /* If they're equal, mark it in the duplicate list. + * We'll add in the original later. + */ + dup_array[dup_count++] = i; + } + } + + /* Check if we need to pick from the duplicate list */ + if (dup_count) + { + /* Re-add the original to the list because it's not in there. */ + dup_array[dup_count++] = (unsigned int)highest; + + /* Pick a random slot. */ + srand((unsigned int)(time(NULL))); + unsigned int r = (unsigned int)rand() % dup_count; + + /* Pick the item. */ + chosen = dup_array[r]; + } else { + chosen = (unsigned int)highest; + } + +picked_item: + m_pHandler->OnMenuVoteEnd(m_pCurMenu, chosen); + m_pHandler->OnMenuEnd(m_pCurMenu); + InternalReset(); +} + +void VoteMenuHandler::OnMenuStart(IBaseMenu *menu) +{ + m_Clients++; +} + +void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu) +{ + DecrementPlayerCount(); +} + +void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) { m_pHandler->OnMenuCancel(menu, client, reason); } -void BroadcastHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display) +void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display) { - numClients++; m_pHandler->OnMenuDisplay(menu, client, display); } -void BroadcastHandler::OnBroadcastEnd(IBaseMenu *menu) +void VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display) { - g_Menus.FreeBroadcastHandler(this); + m_pHandler->OnMenuDisplayItem(menu, client, item, display); } -void BroadcastHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) +void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) { + m_pHandler->OnMenuDrawItem(menu, client, item, style); +} + +void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) +{ + /* Check by our item count, NOT the vote array size */ + if (item < m_Items) + { + m_Votes[item]++; + m_NumVotes++; + } + m_pHandler->OnMenuSelect(menu, client, item); } -void BroadcastHandler::OnMenuEnd(IBaseMenu *menu) +void VoteMenuHandler::Reset(IMenuHandler *mh) { - assert(numClients > 0); - - /* Only fire if all clients have gotten a menu end */ - if (--numClients == 0) - { - IMenuHandler *pHandler = m_pHandler; - OnBroadcastEnd(menu); - pHandler->OnMenuEnd(menu); - } + m_pHandler = mh; + InternalReset(); } -VoteHandler::VoteHandler(IMenuVoteHandler *handler) -: BroadcastHandler(handler), m_pVoteHandler(handler) +void VoteMenuHandler::InternalReset() { -} - -void VoteHandler::Initialize(IBaseMenu *menu) -{ - unsigned int numItems = menu->GetItemCount(); - - if (m_counts.size() >= numItems) - { - for (size_t i=0; i m_counts[highest]) - { - m_ties.clear(); - highest = i; - } else if (m_counts[i] == m_counts[highest]) { - m_ties.push_back(i); - } - } - - if (m_ties.size()) - { - m_ties.push_back(highest); - srand(static_cast(time(NULL))); - highest = m_ties[rand() % m_ties.size()]; - } - - m_pVoteHandler->OnMenuVoteEnd(menu, highest); - - g_Menus.FreeVoteHandler(this); + m_Clients = 0; + m_Items = 0; + m_bStarted = false; + m_pCurMenu = NULL; + m_NumVotes = 0; } /******************************* @@ -169,12 +242,6 @@ void MenuManager::OnSourceModAllShutdown() g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent); g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent); - while (!m_BroadcastHandlers.empty()) - { - delete m_BroadcastHandlers.front(); - m_BroadcastHandlers.pop(); - } - while (!m_VoteHandlers.empty()) { delete m_VoteHandlers.front(); @@ -659,119 +726,34 @@ skip_search: return display; } -#if 0 -unsigned int MenuManager::BroadcastMenu(IBaseMenu *menu, - IMenuHandler *handler, - int clients[], - unsigned int numClients, - unsigned int time) -{ - BroadcastHandler *bh; - - if (m_BroadcastHandlers.empty()) - { - bh = new BroadcastHandler(handler); - } else { - bh = m_BroadcastHandlers.front(); - m_BroadcastHandlers.pop(); - bh->m_pHandler = handler; - bh->numClients = 0; - } - - handler->OnMenuStart(menu); - - unsigned int total = 0; - for (unsigned int i=0; iDisplay(clients[i], bh, time)) - { - continue; - } - - /* :TODO: Allow sourcetv only, not all bots */ - CPlayer *player = g_Players.GetPlayerByIndex(clients[i]); - if (player->IsFakeClient()) - { - continue; - } - - total++; - } - - if (!total) - { - /* End the broadcast here */ - handler->OnMenuEnd(menu); - FreeBroadcastHandler(bh); - } - - return total; -} - -unsigned int MenuManager::VoteMenu(IBaseMenu *menu, - IMenuVoteHandler *handler, - int clients[], - unsigned int numClients, - unsigned int time) -{ - VoteHandler *vh; - - if (m_VoteHandlers.empty()) - { - vh = new VoteHandler(handler); - } else { - vh = m_VoteHandlers.front(); - m_VoteHandlers.pop(); - vh->m_pHandler = handler; - vh->numClients = 0; - } - - vh->Initialize(menu); - handler->OnMenuStart(menu); - - unsigned int total = 0; - for (unsigned int i=0; iDisplay(clients[i], vh, time)) - { - continue; - } - - /* :TODO: Allow sourcetv only, not all bots */ - CPlayer *player = g_Players.GetPlayerByIndex(clients[i]); - if (player->IsFakeClient()) - { - continue; - } - - total++; - } - - if (!total) - { - /* End the broadcast here */ - handler->OnMenuEnd(menu); - FreeVoteHandler(vh); - } - - return total; -} -#endif - -void MenuManager::FreeBroadcastHandler(BroadcastHandler *bh) -{ - m_BroadcastHandlers.push(bh); -} - -void MenuManager::FreeVoteHandler(VoteHandler *vh) -{ - m_VoteHandlers.push(vh); -} - IMenuStyle *MenuManager::GetDefaultStyle() { return m_pDefaultStyle; } +IVoteMenuHandler *MenuManager::CreateVoteWrapper(IMenuHandler *mh) +{ + VoteMenuHandler *vh = NULL; + + if (m_VoteHandlers.empty()) + { + vh = new VoteMenuHandler; + } else { + vh = m_VoteHandlers.front(); + m_VoteHandlers.pop(); + } + + vh->Reset(mh); + + return vh; +} + +void MenuManager::ReleaseVoteWrapper(IVoteMenuHandler *mh) +{ + if (mh == NULL) + { + return; + } + + m_VoteHandlers.push((VoteMenuHandler *)mh); +} diff --git a/core/MenuManager.h b/core/MenuManager.h index 7688afba..365718db 100644 --- a/core/MenuManager.h +++ b/core/MenuManager.h @@ -25,36 +25,36 @@ using namespace SourceMod; using namespace SourceHook; -class BroadcastHandler : public IMenuHandler +class VoteMenuHandler : public IVoteMenuHandler { -public: - BroadcastHandler(IMenuHandler *handler); public: //IMenuHandler + unsigned int GetMenuAPIVersion2(); + void OnMenuStart(IBaseMenu *menu); void OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display); void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); - void OnMenuEnd(IBaseMenu *menu); void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); - unsigned int GetMenuAPIVersion2(); + void OnMenuEnd(IBaseMenu *menu); + void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style); + void OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display); +public: //IVoteMenuHandler + bool IsVoteInProgress(); + void InitializeVoting(IBaseMenu *menu); + void StartVoting(); public: - virtual void OnBroadcastEnd(IBaseMenu *menu); -public: - IMenuHandler *m_pHandler; - unsigned int numClients; -}; - -class VoteHandler : public BroadcastHandler -{ -public: - VoteHandler(IMenuVoteHandler *handler); -public: - void Initialize(IBaseMenu *menu); - void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); - void OnBroadcastEnd(IBaseMenu *menu); + void Reset(IMenuHandler *mh); private: - CVector m_counts; - CVector m_ties; - unsigned int numItems; - IMenuVoteHandler *m_pVoteHandler; + void DecrementPlayerCount(); + void EndVoting(); + void InternalReset(); +private: + IMenuHandler *m_pHandler; + unsigned int m_Clients; + unsigned int m_Items; + CVector m_Votes; + CVector m_Dups; + IBaseMenu *m_pCurMenu; + bool m_bStarted; + unsigned int m_NumVotes; }; class MenuManager : @@ -84,35 +84,24 @@ public: unsigned int GetStyleCount(); IMenuStyle *GetStyle(unsigned int index); IMenuStyle *FindStyleByName(const char *name); - unsigned int BroadcastMenu(IBaseMenu *menu, - IMenuHandler *handler, - int clients[], - unsigned int numClients, - unsigned int time); - unsigned int VoteMenu(IBaseMenu *menu, - IMenuVoteHandler *handler, - int clients[], - unsigned int numClients, - unsigned int time); IMenuStyle *GetDefaultStyle(); void AddStyle(IMenuStyle *style); bool SetDefaultStyle(IMenuStyle *style); IMenuPanel *RenderMenu(int client, menu_states_t &states, ItemOrder order); + IVoteMenuHandler *CreateVoteWrapper(IMenuHandler *mh); + void ReleaseVoteWrapper(IVoteMenuHandler *mh); public: //IHandleTypeDispatch void OnHandleDestroy(HandleType_t type, void *object); public: HandleError ReadMenuHandle(Handle_t handle, IBaseMenu **menu); HandleError ReadStyleHandle(Handle_t handle, IMenuStyle **style); protected: - void FreeBroadcastHandler(BroadcastHandler *bh); - void FreeVoteHandler(VoteHandler *vh); Handle_t CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner); Handle_t CreateStyleHandle(IMenuStyle *style); private: int m_ShowMenu; IMenuStyle *m_pDefaultStyle; - CStack m_BroadcastHandlers; - CStack m_VoteHandlers; + CStack m_VoteHandlers; CVector m_Styles; HandleType_t m_StyleType; HandleType_t m_MenuType; diff --git a/core/MenuStyle_Base.cpp b/core/MenuStyle_Base.cpp index bd01313c..0da6cf92 100644 --- a/core/MenuStyle_Base.cpp +++ b/core/MenuStyle_Base.cpp @@ -434,12 +434,14 @@ bool BaseMenuStyle::RedoClientMenu(int client, ItemOrder order) CBaseMenu::CBaseMenu(IMenuHandler *pHandler, IMenuStyle *pStyle, IdentityToken_t *pOwner) : m_pStyle(pStyle), m_Strings(512), m_Pagination(7), m_ExitButton(true), m_bShouldDelete(false), m_bCancelling(false), m_pOwner(pOwner ? pOwner : g_pCoreIdent), -m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler) +m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler), +m_pVoteHandler(NULL) { } CBaseMenu::~CBaseMenu() { + g_Menus.ReleaseVoteWrapper(m_pVoteHandler); } Handle_t CBaseMenu::GetHandle() @@ -647,3 +649,24 @@ void CBaseMenu::InternalDelete() delete this; } +bool CBaseMenu::BroadcastVote(int clients[], + unsigned int numClients, + unsigned int maxTime, + unsigned int flags) +{ + if (!m_pVoteHandler) + { + m_pVoteHandler = g_Menus.CreateVoteWrapper(m_pHandler); + } else if (m_pVoteHandler->IsVoteInProgress()) { + return false; + } + + m_pVoteHandler->InitializeVoting(this); + + for (unsigned int i=0; iStartVoting(); +} diff --git a/core/MenuStyle_Base.h b/core/MenuStyle_Base.h index fe5f7b11..f81a87f7 100644 --- a/core/MenuStyle_Base.h +++ b/core/MenuStyle_Base.h @@ -111,6 +111,12 @@ public: virtual void Destroy(bool releaseHandle); virtual void Cancel_Finally() =0; virtual Handle_t GetHandle(); + bool BroadcastVote(int clients[], + unsigned int numClients, + unsigned int maxTime, + unsigned int flags=0); +public: + virtual void VoteDisplay(int client, unsigned int maxTime) =0; private: void InternalDelete(); protected: @@ -127,6 +133,7 @@ protected: bool m_bWillFreeHandle; Handle_t m_hHandle; IMenuHandler *m_pHandler; + IVoteMenuHandler *m_pVoteHandler; }; #endif //_INCLUDE_MENUSTYLE_BASE_H diff --git a/core/MenuStyle_Radio.cpp b/core/MenuStyle_Radio.cpp index c8f75a69..5b41125a 100644 --- a/core/MenuStyle_Radio.cpp +++ b/core/MenuStyle_Radio.cpp @@ -366,9 +366,24 @@ IMenuPanel *CRadioMenu::CreatePanel() bool CRadioMenu::Display(int client, unsigned int time) { + if (m_bCancelling) + { + return false; + } + return g_RadioMenuStyle.DoClientMenu(client, this, m_pHandler, time); } +void CRadioMenu::VoteDisplay(int client, unsigned int maxTime) +{ + if (m_bCancelling) + { + return; + } + + g_RadioMenuStyle.DoClientMenu(client, this, m_pVoteHandler, maxTime); +} + void CRadioMenu::Cancel_Finally() { g_RadioMenuStyle.CancelMenu(this); diff --git a/core/MenuStyle_Radio.h b/core/MenuStyle_Radio.h index d19dc245..19691212 100644 --- a/core/MenuStyle_Radio.h +++ b/core/MenuStyle_Radio.h @@ -98,6 +98,7 @@ public: IMenuPanel *CreatePanel(); bool Display(int client, unsigned int time); void Cancel_Finally(); + void VoteDisplay(int client, unsigned int maxTime); }; extern CRadioStyle g_RadioMenuStyle; diff --git a/core/MenuStyle_Valve.cpp b/core/MenuStyle_Valve.cpp index 5c3f2383..36f188a0 100644 --- a/core/MenuStyle_Valve.cpp +++ b/core/MenuStyle_Valve.cpp @@ -375,6 +375,16 @@ bool CValveMenu::Display(int client, unsigned int time) return g_ValveMenuStyle.DoClientMenu(client, this, m_pHandler, time); } +void CValveMenu::VoteDisplay(int client, unsigned int maxTime) +{ + if (m_bCancelling) + { + return; + } + + g_ValveMenuStyle.DoClientMenu(client, this, m_pVoteHandler, maxTime); +} + IMenuPanel *CValveMenu::CreatePanel() { return new CValveMenuDisplay(this); diff --git a/core/MenuStyle_Valve.h b/core/MenuStyle_Valve.h index 97edcfbf..78e31934 100644 --- a/core/MenuStyle_Valve.h +++ b/core/MenuStyle_Valve.h @@ -103,6 +103,7 @@ public: //IBaseMenu bool SetExitButton(bool set); bool SetPagination(unsigned int itemsPerPage); bool Display(int client, unsigned int time); + void VoteDisplay(int client, unsigned int maxTime); public: //CBaseMenu void Cancel_Finally(); private: diff --git a/public/IMenuManager.h b/public/IMenuManager.h index 343d59e2..1ac849f6 100644 --- a/public/IMenuManager.h +++ b/public/IMenuManager.h @@ -23,7 +23,7 @@ #include #define SMINTERFACE_MENUMANAGER_NAME "IMenuManager" -#define SMINTERFACE_MENUMANAGER_VERSION 1 +#define SMINTERFACE_MENUMANAGER_VERSION 2 /** * @file IMenuManager.h @@ -498,6 +498,23 @@ namespace SourceMod * @return Handle_t handle value. */ virtual Handle_t GetHandle() =0; + + /** + * @brief Sends a menu to multiple clients as a vote menu. All callbacks + * will be sent as normal, except two extras, OnMenuVoteStart and + * OnMenuVoteEnd, will be called. + * + * @param clients Array of client indexes. + * @param numClients Number of client indexes in the array. + * @param maxTime Maximum amount of time to hold the vote. + * @param flags Optional voting flags (currently unused). + * @return True on success, false if a vote is already in + * progress (the menu must be cancelled first). + */ + virtual bool BroadcastVote(int clients[], + unsigned int numClients, + unsigned int maxTime, + unsigned int flags=0) =0; }; /** @@ -600,21 +617,55 @@ namespace SourceMod virtual void OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display) { } - }; - /** - * @brief Handles a vote menu. - */ - class IMenuVoteHandler : public IMenuHandler - { - public: /** - * @brief Called when a vote ends. + * @brief Called when a vote has been started and displayed to + * clients. This is called after OnMenuStart() and OnMenuDisplay(), + * but before OnMenuSelect(). + * + * @param menu Menu pointer. + */ + virtual void OnMenuVoteStart(IBaseMenu *menu) + { + } + + /** + * @brief Called when a vote ends. This is automatically called by the + * wrapper, and never needs to called from a style implementation. * * @param menu Menu pointer. * @param item Item position that was chosen by a majority. */ - virtual void OnMenuVoteEnd(IBaseMenu *menu, unsigned int item) =0; + virtual void OnMenuVoteEnd(IBaseMenu *menu, unsigned int item) + { + } + }; + + /** + * @brief Contains functions for managing a vote handler. + */ + class IVoteMenuHandler : public IMenuHandler + { + public: + /** + * @brief Returns whether or not a vote is in progress. + * + * @return True if a vote is in progress, false otherwise. + */ + virtual bool IsVoteInProgress() =0; + + /** + * @brief Use this to mark the vote as in progress (start). + * + * @param menu Menu pointer. + */ + virtual void InitializeVoting(IBaseMenu *menu) =0; + + /** + * @brief Use this to notify that all clients' displays have been + * processed (i.e., there are no more clients to display to). + */ + virtual void StartVoting() =0; }; /** @@ -640,39 +691,6 @@ namespace SourceMod */ virtual IMenuStyle *FindStyleByName(const char *name) =0; -#if 0 - /** - * @brief Broadcasts a menu to a number of clients. - * - * @param menu Menu pointer. - * @param clients Array of client indexes. - * @param numClients Number of clients in the array. - * @param time Time to hold the menu. - * @return Number of clients the menu will be waiting on. - */ - virtual unsigned int BroadcastMenu(IBaseMenu *menu, - IMenuHandler *handler, - int clients[], - unsigned int numClients, - unsigned int time) =0; - - /** - * @brief Broadcasts a menu to a number of clients as a vote menu. - * - * @param menu Menu pointer. - * @param handler IMenuHandler pointer. - * @param clients Array of client indexes. - * @param numClients Number of clients in the array. - * @param time Time to hold the menu. - * @return Number of clients the menu will be waiting on. - */ - virtual unsigned int VoteMenu(IBaseMenu *menu, - IMenuVoteHandler *handler, - int clients[], - unsigned int numClients, - unsigned int time) =0; -#endif - /** * @brief Returns the default draw style Core is using. * @@ -695,6 +713,27 @@ namespace SourceMod * be freed using IMenuPanel::DeleteThis. */ virtual IMenuPanel *RenderMenu(int client, menu_states_t &states, ItemOrder order) =0; + + /** + * @brief Creates a standard voting wrapper. The wrapper is not + * re-entrant; a second menu cannot be displayed on the same handler + * at the same time. + * + * @param mh Menu handler to wrap around. + * @return An IMenuHandler pointer that is a wrapper + * around IMenuHandler callbacks to invoke + * voting related callbacks. + */ + virtual IVoteMenuHandler *CreateVoteWrapper(IMenuHandler *mh) =0; + + /** + * @brief Frees a standard voting wrapper. + * + * @param mh Menu handler pointer created by + * CreateVoteWrapper(). NULL values will be + * safely ignored. + */ + virtual void ReleaseVoteWrapper(IVoteMenuHandler *mh) =0; }; }