From 8e07790997dce770a9590ae6a3719a2d1753c0cf Mon Sep 17 00:00:00 2001 From: BotoX Date: Wed, 25 Sep 2019 20:26:08 +0200 Subject: [PATCH] Implement per-client randomized menus with MenuShufflePerClient native. Add MenuSetClientMapping native. --- core/MenuManager.cpp | 6 ++-- core/MenuStyle_Base.cpp | 67 +++++++++++++++++++++++++++++++++++++-- core/MenuStyle_Base.h | 13 ++++++-- core/MenuVoting.cpp | 7 ++-- core/logic/smn_menus.cpp | 63 +++++++++++++++++++++++++++++++++++- plugins/include/menus.inc | 39 +++++++++++++++++++++-- public/IMenuManager.h | 34 ++++++++++++++++++-- 7 files changed, 213 insertions(+), 16 deletions(-) diff --git a/core/MenuManager.cpp b/core/MenuManager.cpp index 6554465b..baad12c4 100644 --- a/core/MenuManager.cpp +++ b/core/MenuManager.cpp @@ -308,7 +308,7 @@ IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder ord { ItemDrawInfo &dr = drawItems[foundItems].draw; /* Is the item valid? */ - if (menu->GetItemInfo(i, &dr) != NULL) + if (menu->GetItemInfo(i, &dr, client) != NULL) { /* Ask the user to change the style, if necessary */ mh->OnMenuDrawItem(menu, client, i, dr.style); @@ -398,7 +398,7 @@ IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder ord } while (++lastItem < totalItems) { - if (menu->GetItemInfo(lastItem, &dr) != NULL) + if (menu->GetItemInfo(lastItem, &dr, client) != NULL) { mh->OnMenuDrawItem(menu, client, lastItem, dr.style); if (IsSlotItem(panel, dr.style)) @@ -420,7 +420,7 @@ IMenuPanel *MenuManager::RenderMenu(int client, menu_states_t &md, ItemOrder ord lastItem--; while (lastItem != 0) { - if (menu->GetItemInfo(lastItem, &dr) != NULL) + if (menu->GetItemInfo(lastItem, &dr, client) != NULL) { mh->OnMenuDrawItem(menu, client, lastItem, dr.style); if (IsSlotItem(panel, dr.style)) diff --git a/core/MenuStyle_Base.cpp b/core/MenuStyle_Base.cpp index 415029c1..cab25f1c 100644 --- a/core/MenuStyle_Base.cpp +++ b/core/MenuStyle_Base.cpp @@ -633,7 +633,7 @@ bool CBaseMenu::AppendItem(const char *info, const ItemDrawInfo &draw) return false; } - CItem item; + CItem item(m_items.length()); item.info = info; if (draw.display) @@ -655,7 +655,7 @@ bool CBaseMenu::InsertItem(unsigned int position, const char *info, const ItemDr if (position >= m_items.length()) return false; - CItem item; + CItem item(position); item.info = info; if (draw.display) item.display = new ke::AString(draw.display); @@ -679,11 +679,16 @@ void CBaseMenu::RemoveAllItems() m_items.clear(); } -const char *CBaseMenu::GetItemInfo(unsigned int position, ItemDrawInfo *draw/* =NULL */) +const char *CBaseMenu::GetItemInfo(unsigned int position, ItemDrawInfo *draw/* =NULL */, int client/* =0 */) { if (position >= m_items.length()) return NULL; + if (client > 0 && position < m_RandomMaps[client].length()) + { + position = m_RandomMaps[client][position]; + } + if (draw) { draw->display = m_items[position].display->chars(); @@ -693,6 +698,62 @@ const char *CBaseMenu::GetItemInfo(unsigned int position, ItemDrawInfo *draw/* = return m_items[position].info.chars(); } +void CBaseMenu::ShufflePerClient(int start, int stop) +{ + // limit map len to 255 items since it's using uint8 + int length = MIN(GetItemCount(), 255); + if (stop >= 0) + length = MIN(length, stop); + + for (int i = 1; i < SM_MAXPLAYERS + 1; i++) + { + // populate per-client map ... + m_RandomMaps[i].resize(length); + for (int j = 0; j < length; j++) + m_RandomMaps[i][j] = j; + + // ... and random shuffle it + for (int j = length - 1; j > start; j--) + { + int x = rand() % (j - start + 1) + start; + uint8_t tmp = m_RandomMaps[i][x]; + m_RandomMaps[i][x] = m_RandomMaps[i][j]; + m_RandomMaps[i][j] = tmp; + } + } +} + +void CBaseMenu::SetClientMapping(int client, int *array, int length) +{ + length = MIN(length, 255); + m_RandomMaps[client].resize(length); + for (int i = 0; i < length; i++) + { + m_RandomMaps[client][i] = array[i]; + } +} + +bool CBaseMenu::IsPerClientShuffled() +{ + for (int i = 1; i < SM_MAXPLAYERS + 1; i++) + { + if(m_RandomMaps[i].length() > 0) + return true; + } + return false; +} + +unsigned int CBaseMenu::GetRealItemIndex(int client, unsigned int position) +{ + if (client > 0 && position < m_RandomMaps[client].length()) + { + position = m_RandomMaps[client][position]; + return m_items[position].index; + } + + return position; +} + unsigned int CBaseMenu::GetItemCount() { return m_items.length(); diff --git a/core/MenuStyle_Base.h b/core/MenuStyle_Base.h index e9d83ebf..acc58622 100644 --- a/core/MenuStyle_Base.h +++ b/core/MenuStyle_Base.h @@ -44,8 +44,9 @@ using namespace SourceMod; class CItem { public: - CItem() + CItem(unsigned int index) { + this->index = index; style = 0; access = 0; } @@ -53,11 +54,13 @@ public: : info(ke::Move(other.info)), display(ke::Move(other.display)) { + index = other.index; style = other.style; access = other.access; } CItem & operator =(CItem &&other) { + index = other.index; info = ke::Move(other.info); display = ke::Move(other.display); style = other.style; @@ -66,6 +69,7 @@ public: } public: + unsigned int index; ke::AString info; ke::AutoPtr display; unsigned int style; @@ -138,7 +142,7 @@ public: virtual bool InsertItem(unsigned int position, const char *info, const ItemDrawInfo &draw); virtual bool RemoveItem(unsigned int position); virtual void RemoveAllItems(); - virtual const char *GetItemInfo(unsigned int position, ItemDrawInfo *draw=NULL); + virtual const char *GetItemInfo(unsigned int position, ItemDrawInfo *draw=NULL, int client=0); virtual unsigned int GetItemCount(); virtual bool SetPagination(unsigned int itemsPerPage); virtual unsigned int GetPagination(); @@ -152,6 +156,10 @@ public: virtual unsigned int GetMenuOptionFlags(); virtual void SetMenuOptionFlags(unsigned int flags); virtual IMenuHandler *GetHandler(); + virtual void ShufflePerClient(int start, int stop); + virtual void SetClientMapping(int client, int *array, int length); + virtual bool IsPerClientShuffled(); + virtual unsigned int GetRealItemIndex(int client, unsigned int position); unsigned int GetBaseMemUsage(); private: void InternalDelete(); @@ -168,6 +176,7 @@ protected: Handle_t m_hHandle; IMenuHandler *m_pHandler; unsigned int m_nFlags; + ke::Vector m_RandomMaps[SM_MAXPLAYERS+1]; }; #endif //_INCLUDE_MENUSTYLE_BASE_H diff --git a/core/MenuVoting.cpp b/core/MenuVoting.cpp index 0a873b67..93925c6b 100644 --- a/core/MenuVoting.cpp +++ b/core/MenuVoting.cpp @@ -514,15 +514,16 @@ void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int ite /* Check by our item count, NOT the vote array size */ if (item < m_Items) { - m_ClientVotes[client] = item; - m_Votes[item]++; + unsigned int index = menu->GetRealItemIndex(client, item); + m_ClientVotes[client] = index; + m_Votes[index]++; m_NumVotes++; if (sm_vote_chat.GetBool() || sm_vote_console.GetBool() || sm_vote_client_console.GetBool()) { static char buffer[1024]; ItemDrawInfo dr; - menu->GetItemInfo(item, &dr); + menu->GetItemInfo(item, &dr, client); if (sm_vote_console.GetBool()) { diff --git a/core/logic/smn_menus.cpp b/core/logic/smn_menus.cpp index 7b220d74..e2570718 100644 --- a/core/logic/smn_menus.cpp +++ b/core/logic/smn_menus.cpp @@ -816,8 +816,14 @@ static cell_t GetMenuItem(IPluginContext *pContext, const cell_t *params) ItemDrawInfo dr; const char *info; + cell_t client = (params[0] >= 8) ? params[8] : 0; + if(!client && menu->IsPerClientShuffled()) + { + return pContext->ThrowNativeError("This menu has been per-client random shuffled. " + "You have to call GetMenuItem with a client index!"); + } - if ((info=menu->GetItemInfo(params[2], &dr)) == NULL) + if ((info=menu->GetItemInfo(params[2], &dr, client)) == NULL) { return 0; } @@ -832,6 +838,57 @@ static cell_t GetMenuItem(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t MenuShufflePerClient(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err = ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + int start = params[2]; + int stop = params[3]; + + if (stop > 0 && !(stop >= start)) + { + return pContext->ThrowNativeError("Stop must be -1 or >= start!"); + } + + menu->ShufflePerClient(start, stop); + + return 1; +} + +static cell_t MenuSetClientMapping(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err = ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + int client = params[2]; + if (client < 1 || client > SM_MAXPLAYERS) + { + return pContext->ThrowNativeError("Invalid client index!"); + } + + cell_t *array; + pContext->LocalToPhysAddr(params[3], &array); + + int length = params[4]; + + menu->SetClientMapping(client, array, length); + + return 1; +} + static cell_t SetMenuPagination(IPluginContext *pContext, const cell_t *params) { Handle_t hndl = (Handle_t)params[1]; @@ -1645,6 +1702,8 @@ REGISTER_NATIVES(menuNatives) {"SetPanelKeys", SetPanelKeys}, {"SetVoteResultCallback", SetVoteResultCallback}, {"VoteMenu", VoteMenu}, + {"MenuShufflePerClient", MenuShufflePerClient}, + {"MenuSetClientMapping", MenuSetClientMapping}, {"SetMenuNoVoteButton", SetMenuNoVoteButton}, // Transitional syntax support. @@ -1673,6 +1732,8 @@ REGISTER_NATIVES(menuNatives) {"Menu.ToPanel", CreatePanelFromMenu}, {"Menu.Cancel", CancelMenu}, {"Menu.DisplayVote", VoteMenu}, + {"Menu.ShufflePerClient", MenuShufflePerClient}, + {"Menu.SetClientMapping", MenuSetClientMapping}, {"Menu.Pagination.get", GetMenuPagination}, {"Menu.Pagination.set", SetMenuPagination}, {"Menu.OptionFlags.get", GetMenuOptionFlags}, diff --git a/plugins/include/menus.inc b/plugins/include/menus.inc index b7fae0e9..5923ece2 100644 --- a/plugins/include/menus.inc +++ b/plugins/include/menus.inc @@ -307,9 +307,23 @@ methodmap Menu < Handle // @param style By-reference variable to store drawing flags. // @param dispBuf Display buffer. // @param dispBufLen Maximum length of the display buffer. + // @param client Client index. Must be specified if menu is per-client random shuffled, -1 to ignore. // @return True on success, false if position is invalid. public native bool GetItem(int position, char[] infoBuf, int infoBufLen, - int &style=0, char[] dispBuf="", int dispBufLen=0); + int &style=0, char[] dispBuf="", int dispBufLen=0, int client=0); + + // Generates a per-client random mapping for the current vote options. + // + // @param start Menu item index to start randomizing from. + // @param stop Menu item index to stop randomizing at. -1 = infinite + public native void ShufflePerClient(int start=0, int stop=-1); + + // Fills the client vote option mapping with user supplied values. + // + // @param client Client index. + // @param array Integer array with mapping. + // @param length Length of array. + public native void SetClientMapping(int client, int[] array, int length); // Sets the menu's default title/instruction message. // @@ -537,6 +551,7 @@ native void RemoveAllMenuItems(Handle menu); * @param style By-reference variable to store drawing flags. * @param dispBuf Display buffer. * @param dispBufLen Maximum length of the display buffer. + * @param client Client index. Must be specified if menu is per-client random shuffled, -1 to ignore. * @return True on success, false if position is invalid. * @error Invalid Handle. */ @@ -546,7 +561,27 @@ native bool GetMenuItem(Handle menu, int infoBufLen, int &style=0, char[] dispBuf="", - int dispBufLen=0); + int dispBufLen=0, + int client=0); + +/** + * Generates a per-client random mapping for the current vote options. + * + * @param menu Menu Handle. + * @param start Menu item index to start randomizing from. + * @param stop Menu item index to stop randomizing at. -1 = infinite + */ +native void MenuShufflePerClient(Handle menu, int start=0, int stop=-1); + +/* + * Fills the client vote option mapping with user supplied values. + * + * @param menu Menu Handle. + * @param client Client index. + * @param array Integer array with mapping. + * @param length Length of array. + */ +native void MenuSetClientMapping(Handle menu, int client, int[] array, int length); /** * Returns the first item on the page of a currently selected menu. diff --git a/public/IMenuManager.h b/public/IMenuManager.h index 233cf430..9b0978b6 100644 --- a/public/IMenuManager.h +++ b/public/IMenuManager.h @@ -36,7 +36,7 @@ #include #define SMINTERFACE_MENUMANAGER_NAME "IMenuManager" -#define SMINTERFACE_MENUMANAGER_VERSION 16 +#define SMINTERFACE_MENUMANAGER_VERSION 17 /** * @file IMenuManager.h @@ -485,9 +485,10 @@ namespace SourceMod * * @param position Position, starting from 0. * @param draw Optional pointer to store a draw information. + * @param client Client index. (Important for randomized menus.) * @return Info string pointer, or NULL if position was invalid. */ - virtual const char *GetItemInfo(unsigned int position, ItemDrawInfo *draw) =0; + virtual const char *GetItemInfo(unsigned int position, ItemDrawInfo *draw, int client=0) =0; /** * @brief Returns the number of items. @@ -636,6 +637,35 @@ namespace SourceMod * @return Approximate number of bytes being used. */ virtual unsigned int GetApproxMemUsage() =0; + + /** + * @brief Generates a per-client random mapping for the current vote options. + * @param start Menu item index to start randomizing from. + * @param stop Menu item index to stop randomizing at. -1 = infinite + */ + virtual void ShufflePerClient(int start=0, int stop=-1) =0; + + /** + * @brief Fills the client vote option mapping with user supplied values. + * @param client Client index. + * @param array Integer array with mapping. + * @param length Length of array. + */ + virtual void SetClientMapping(int client, int *array, int length) =0; + + /** + * @brief Returns true if the menu has a per-client random mapping. + * @return True on success, false otherwise. + */ + virtual bool IsPerClientShuffled() =0; + + /** + * @brief Returns the actual (not shuffled) item index from the client position. + * @param client Client index. + * @param position Position, starting from 0. + * @return Actual item index in menu. + */ + virtual unsigned int GetRealItemIndex(int client, unsigned int position) =0; }; /**