diff --git a/core/MenuManager.cpp b/core/MenuManager.cpp index 53d1d03f..c7a5bdd5 100644 --- a/core/MenuManager.cpp +++ b/core/MenuManager.cpp @@ -21,6 +21,7 @@ #include "PlayerManager.h" #include "MenuStyle_Valve.h" #include "ShareSys.h" +#include "HandleSys.h" MenuManager g_Menus; @@ -150,10 +151,24 @@ MenuManager::MenuManager() void MenuManager::OnSourceModAllInitialized() { g_ShareSys.AddInterface(NULL, this); + + HandleAccess access; + g_HandleSys.InitAccessDefaults(NULL, &access); + + /* Deny cloning to menus */ + access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY; + m_MenuType = g_HandleSys.CreateType("IBaseMenu", this, 0, NULL, &access, g_pCoreIdent, NULL); + + /* Also deny deletion to styles */ + access.access[HandleAccess_Delete] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY; + m_StyleType = g_HandleSys.CreateType("IMenuStyle", this, 0, NULL, &access, g_pCoreIdent, NULL); } 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(); @@ -167,6 +182,57 @@ void MenuManager::OnSourceModAllShutdown() } } +void MenuManager::OnHandleDestroy(HandleType_t type, void *object) +{ + if (type == m_MenuType) + { + IBaseMenu *menu = (IBaseMenu *)object; + menu->Destroy(false); + } else if (type == m_StyleType) { + /* Do nothing */ + } +} + +Handle_t MenuManager::CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner) +{ + if (m_MenuType == NO_HANDLE_TYPE) + { + return BAD_HANDLE; + } + + return g_HandleSys.CreateHandle(m_MenuType, menu, pOwner, g_pCoreIdent, NULL); +} + +Handle_t MenuManager::CreateStyleHandle(IMenuStyle *style) +{ + if (m_StyleType == NO_HANDLE_TYPE) + { + return BAD_HANDLE; + } + + return g_HandleSys.CreateHandle(m_StyleType, style, g_pCoreIdent, g_pCoreIdent, NULL); +} + +HandleError MenuManager::ReadMenuHandle(Handle_t handle, IBaseMenu **menu) +{ + HandleSecurity sec; + + sec.pIdentity = g_pCoreIdent; + sec.pOwner = NULL; + + return g_HandleSys.ReadHandle(handle, m_MenuType, &sec, (void **)menu); +} + +HandleError MenuManager::ReadStyleHandle(Handle_t handle, IMenuStyle **style) +{ + HandleSecurity sec; + + sec.pIdentity = g_pCoreIdent; + sec.pOwner = g_pCoreIdent; + + return g_HandleSys.ReadHandle(handle, m_StyleType, &sec, (void **)style); +} + bool MenuManager::SetDefaultStyle(IMenuStyle *style) { if (!style) @@ -503,6 +569,13 @@ skip_search: ItemDrawInfo padItem(NULL, ITEMDRAW_SPACER); if (exitButton || (displayNext || displayPrev)) { + /* If there are no control options, + * Instead just pad with invisible slots. + */ + if (!displayPrev && !displayPrev) + { + padItem.style = ITEMDRAW_NOTEXT; + } /* Add spacers so we can pad to the end */ for (unsigned int i=0; i m_BroadcastHandlers; CStack m_VoteHandlers; CVector m_Styles; + HandleType_t m_StyleType; + HandleType_t m_MenuType; }; extern MenuManager g_Menus; diff --git a/core/MenuStyle_Base.cpp b/core/MenuStyle_Base.cpp index 40a90684..f372c92f 100644 --- a/core/MenuStyle_Base.cpp +++ b/core/MenuStyle_Base.cpp @@ -17,11 +17,23 @@ #include "MenuStyle_Base.h" #include "PlayerManager.h" #include "MenuManager.h" +#include "HandleSys.h" -BaseMenuStyle::BaseMenuStyle() : m_WatchList(256) +BaseMenuStyle::BaseMenuStyle() : m_WatchList(256), m_hHandle(BAD_HANDLE) { } +Handle_t BaseMenuStyle::GetHandle() +{ + /* Don't create the handle until we need it */ + if (m_hHandle == BAD_HANDLE) + { + m_hHandle = g_Menus.CreateStyleHandle(this); + } + + return m_hHandle; +} + void BaseMenuStyle::AddClientToWatch(int client) { m_WatchList.push_back(client); @@ -56,7 +68,12 @@ void BaseMenuStyle::_CancelClientMenu(int client, bool bAutoIgnore/* =false */, /* Fire callbacks */ mh->OnMenuCancel(menu, client, reason); - mh->OnMenuEnd(menu); + + /* Only fire end if there's a valid menu */ + if (menu) + { + mh->OnMenuEnd(menu); + } if (bAutoIgnore) { @@ -145,6 +162,7 @@ void BaseMenuStyle::OnClientDisconnected(int client) _CancelClientMenu(client, true, MenuCancel_Disconnect); player->bInMenu = false; + player->bInExternMenu = false; } static int do_lookup[256]; @@ -248,7 +266,11 @@ void BaseMenuStyle::ClientPressedKey(int client, unsigned int key_press) mh->OnMenuSelect(menu, client, item); } - mh->OnMenuEnd(menu); + /* Only fire end for valid menus */ + if (menu) + { + mh->OnMenuEnd(menu); + } } bool BaseMenuStyle::DoClientMenu(int client, IMenuPanel *menu, IMenuHandler *mh, unsigned int time) @@ -276,13 +298,7 @@ bool BaseMenuStyle::DoClientMenu(int client, IMenuPanel *menu, IMenuHandler *mh, menu_states_t &states = player->states; if (player->bInMenu) { - /* We need to cancel the old menu */ - if (player->menuHoldTime) - { - RemoveClientFromWatch(client); - } - states.mh->OnMenuCancel(states.menu, client, MenuCancel_Interrupt); - states.mh->OnMenuEnd(states.menu); + _CancelClientMenu(client, true); } states.firstItem = 0; @@ -291,6 +307,7 @@ bool BaseMenuStyle::DoClientMenu(int client, IMenuPanel *menu, IMenuHandler *mh, states.mh = mh; states.apiVers = SMINTERFACE_MENUMANAGER_VERSION; player->bInMenu = true; + player->bInExternMenu = false; player->menuStartTime = gpGlobals->curtime; player->menuHoldTime = time; @@ -367,6 +384,7 @@ bool BaseMenuStyle::DoClientMenu(int client, CBaseMenu *menu, IMenuHandler *mh, /* Finally, set our states */ player->bInMenu = true; + player->bInExternMenu = false; player->menuStartTime = gpGlobals->curtime; player->menuHoldTime = time; @@ -413,8 +431,10 @@ bool BaseMenuStyle::RedoClientMenu(int client, ItemOrder order) return true; } -CBaseMenu::CBaseMenu(IMenuStyle *pStyle) : -m_pStyle(pStyle), m_Strings(512), m_Pagination(7), m_ExitButton(true), m_bShouldDelete(false), m_bCancelling(false) +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) { } @@ -422,6 +442,16 @@ CBaseMenu::~CBaseMenu() { } +Handle_t CBaseMenu::GetHandle() +{ + if (!m_hHandle) + { + m_hHandle = g_Menus.CreateMenuHandle(this, m_pOwner); + } + + return m_hHandle; +} + bool CBaseMenu::AppendItem(const char *info, const ItemDrawInfo &draw) { if (m_Pagination == (unsigned)MENU_NO_PAGINATION @@ -572,18 +602,48 @@ void CBaseMenu::Cancel() if (m_bShouldDelete) { - delete this; + InternalDelete(); } } -void CBaseMenu::Destroy() +void CBaseMenu::Destroy(bool releaseHandle) { + /* Check if we shouldn't be here */ + if (m_bDeleting) + { + return; + } + + /* Save the destruction hint about our handle */ + m_bWillFreeHandle = releaseHandle; + + /* Now actually do stuff */ if (!m_bCancelling || m_bShouldDelete) { Cancel(); - delete this; + InternalDelete(); } else { m_bShouldDelete = true; } } +void CBaseMenu::InternalDelete() +{ + if (m_bWillFreeHandle && m_hHandle != BAD_HANDLE) + { + Handle_t hndl = m_hHandle; + HandleSecurity sec; + + sec.pOwner = m_pOwner; + sec.pIdentity = g_pCoreIdent; + + m_hHandle = BAD_HANDLE; + m_bDeleting = true; + g_HandleSys.FreeHandle(hndl, &sec); + } + + m_pHandler->OnMenuDestroy(this); + + delete this; +} + diff --git a/core/MenuStyle_Base.h b/core/MenuStyle_Base.h index 8a166d58..49707ebd 100644 --- a/core/MenuStyle_Base.h +++ b/core/MenuStyle_Base.h @@ -65,6 +65,7 @@ public: public: //IMenuStyle bool CancelClientMenu(int client, bool autoIgnore/* =false */); MenuSource GetClientMenu(int client, void **object); + Handle_t GetHandle(); public: //IClientListener void OnClientDisconnected(int client); public: //what derived must implement @@ -84,12 +85,13 @@ protected: bool RedoClientMenu(int client, ItemOrder order); protected: FastLink m_WatchList; + Handle_t m_hHandle; }; class CBaseMenu : public IBaseMenu { public: - CBaseMenu(IMenuStyle *pStyle); + CBaseMenu(IMenuHandler *pHandler, IMenuStyle *pStyle, IdentityToken_t *pOwner); virtual ~CBaseMenu(); public: virtual bool AppendItem(const char *info, const ItemDrawInfo &draw); @@ -106,8 +108,11 @@ public: virtual bool GetExitButton(); virtual bool SetExitButton(bool set); virtual void Cancel(); - virtual void Destroy(); + virtual void Destroy(bool releaseHandle); virtual void Cancel_Finally() =0; + virtual Handle_t GetHandle(); +private: + void InternalDelete(); protected: String m_Title; IMenuStyle *m_pStyle; @@ -117,6 +122,11 @@ protected: bool m_ExitButton; bool m_bShouldDelete; bool m_bCancelling; + bool m_bDeleting; + bool m_bWillFreeHandle; + IdentityToken_t *m_pOwner; + Handle_t m_hHandle; + IMenuHandler *m_pHandler; }; #endif //_INCLUDE_MENUSTYLE_BASE_H diff --git a/core/MenuStyle_Radio.cpp b/core/MenuStyle_Radio.cpp index a885fa27..2f78409b 100644 --- a/core/MenuStyle_Radio.cpp +++ b/core/MenuStyle_Radio.cpp @@ -56,6 +56,12 @@ void CRadioStyle::OnSourceModLevelChange(const char *mapName) void CRadioStyle::OnSourceModShutdown() { g_UserMsgs.UnhookUserMessage(g_ShowMenuId, this, false); + + while (!m_FreeDisplays.empty()) + { + delete m_FreeDisplays.front(); + m_FreeDisplays.pop(); + } } bool CRadioStyle::IsSupported() @@ -125,12 +131,12 @@ void CRadioStyle::SendDisplay(int client, IMenuPanel *display) IMenuPanel *CRadioStyle::CreatePanel() { - return new CRadioDisplay(); + return g_RadioMenuStyle.MakeRadioDisplay(); } -IBaseMenu *CRadioStyle::CreateMenu() +IBaseMenu *CRadioStyle::CreateMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner) { - return new CRadioMenu(); + return new CRadioMenu(pHandler, pOwner); } unsigned int CRadioStyle::GetMaxPageItems() @@ -148,6 +154,25 @@ CBaseMenuPlayer *CRadioStyle::GetMenuPlayer(int client) return &m_players[client]; } +CRadioDisplay *CRadioStyle::MakeRadioDisplay(CRadioMenu *menu) +{ + CRadioDisplay *display; + if (m_FreeDisplays.empty()) + { + display = new CRadioDisplay(); + } else { + display = m_FreeDisplays.front(); + m_FreeDisplays.pop(); + display->Reset(); + } + return display; +} + +void CRadioStyle::FreeRadioDisplay(CRadioDisplay *display) +{ + m_FreeDisplays.push(display); +} + CRadioDisplay::CRadioDisplay() { Reset(); @@ -292,7 +317,8 @@ void CRadioDisplay::DeleteThis() delete this; } -CRadioMenu::CRadioMenu() : CBaseMenu(&g_RadioMenuStyle) +CRadioMenu::CRadioMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner) : +CBaseMenu(pHandler, &g_RadioMenuStyle, pOwner) { } @@ -303,12 +329,12 @@ bool CRadioMenu::SetExtOption(MenuOption option, const void *valuePtr) IMenuPanel *CRadioMenu::CreatePanel() { - return new CRadioDisplay(this); + return g_RadioMenuStyle.MakeRadioDisplay(this); } -bool CRadioMenu::Display(int client, IMenuHandler *handler, unsigned int time) +bool CRadioMenu::Display(int client, unsigned int time) { - return g_RadioMenuStyle.DoClientMenu(client, this, handler, time); + return g_RadioMenuStyle.DoClientMenu(client, this, m_pHandler, time); } void CRadioMenu::Cancel_Finally() diff --git a/core/MenuStyle_Radio.h b/core/MenuStyle_Radio.h index 59eb8282..eac5cb4d 100644 --- a/core/MenuStyle_Radio.h +++ b/core/MenuStyle_Radio.h @@ -22,9 +22,13 @@ #include #include #include "sm_fastlink.h" +#include using namespace SourceMod; +class CRadioDisplay; +class CRadioMenu; + class CRadioStyle : public BaseMenuStyle, public SMGlobalClass, @@ -41,7 +45,7 @@ public: //BaseMenuStyle public: //IMenuStyle const char *GetStyleName(); IMenuPanel *CreatePanel(); - IBaseMenu *CreateMenu(); + IBaseMenu *CreateMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner); unsigned int GetMaxPageItems(); public: //IUserMessageListener void OnUserMessage(int msg_id, bf_write *bf, IRecipientFilter *pFilter); @@ -49,14 +53,17 @@ public: //IUserMessageListener public: bool IsSupported(); bool OnClientCommand(int client); +public: + CRadioDisplay *MakeRadioDisplay(CRadioMenu *menu=NULL); + void FreeRadioDisplay(CRadioDisplay *display); private: CBaseMenuPlayer *m_players; + CStack m_FreeDisplays; }; -class CRadioMenu; - class CRadioDisplay : public IMenuPanel { + friend class CRadioStyle; public: CRadioDisplay(); CRadioDisplay(CRadioMenu *menu); @@ -81,11 +88,11 @@ private: class CRadioMenu : public CBaseMenu { public: - CRadioMenu(); + CRadioMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner); public: bool SetExtOption(MenuOption option, const void *valuePtr); IMenuPanel *CreatePanel(); - bool Display(int client, IMenuHandler *handler, unsigned int time); + bool Display(int client, unsigned int time); void Cancel_Finally(); }; diff --git a/core/MenuStyle_Valve.cpp b/core/MenuStyle_Valve.cpp index 68b7365e..de9f2dd9 100644 --- a/core/MenuStyle_Valve.cpp +++ b/core/MenuStyle_Valve.cpp @@ -109,9 +109,9 @@ IMenuPanel *ValveMenuStyle::CreatePanel() return new CValveMenuDisplay(); } -IBaseMenu *ValveMenuStyle::CreateMenu() +IBaseMenu *ValveMenuStyle::CreateMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner) { - return new CValveMenu(); + return new CValveMenu(pHandler, pOwner); } const char *ValveMenuStyle::GetStyleName() @@ -305,7 +305,8 @@ bool CValveMenuDisplay::SendDisplay(int client, IMenuHandler *handler, unsigned return g_ValveMenuStyle.DoClientMenu(client, this, handler, time); } -CValveMenu::CValveMenu() : CBaseMenu(&g_ValveMenuStyle), +CValveMenu::CValveMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner) : +CBaseMenu(pHandler, &g_ValveMenuStyle, pOwner), m_IntroColor(255, 0, 0, 255) { strcpy(m_IntroMsg, "You have a menu, press ESC"); @@ -344,14 +345,14 @@ bool CValveMenu::SetExtOption(MenuOption option, const void *valuePtr) return false; } -bool CValveMenu::Display(int client, IMenuHandler *handler, unsigned int time) +bool CValveMenu::Display(int client, unsigned int time) { if (m_bCancelling) { return false; } - return g_ValveMenuStyle.DoClientMenu(client, this, handler, time); + return g_ValveMenuStyle.DoClientMenu(client, this, m_pHandler, time); } IMenuPanel *CValveMenu::CreatePanel() diff --git a/core/MenuStyle_Valve.h b/core/MenuStyle_Valve.h index ab6e5d17..a4c48df7 100644 --- a/core/MenuStyle_Valve.h +++ b/core/MenuStyle_Valve.h @@ -55,7 +55,7 @@ public: //SMGlobalClass public: //IMenuStyle const char *GetStyleName(); IMenuPanel *CreatePanel(); - IBaseMenu *CreateMenu(); + IBaseMenu *CreateMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner); unsigned int GetMaxPageItems(); private: void HookCreateMessage(edict_t *pEdict, DIALOG_TYPE type, KeyValues *kv, IServerPluginCallbacks *plugin); @@ -92,14 +92,14 @@ class CValveMenu : public CBaseMenu { friend class CValveMenuDisplay; public: - CValveMenu(); + CValveMenu(IMenuHandler *pHandler, IdentityToken_t *pOwner); public: //IBaseMenu bool SetExtOption(MenuOption option, const void *valuePtr); IMenuPanel *CreatePanel(); bool GetExitButton(); bool SetExitButton(bool set); bool SetPagination(unsigned int itemsPerPage); - bool Display(int client, IMenuHandler *handler, unsigned int time); + bool Display(int client, unsigned int time); public: //CBaseMenu void Cancel_Finally(); private: diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index bcd11703..e7cd8617 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -842,6 +842,10 @@ RelativePath="..\smn_lang.cpp" > + + diff --git a/core/smn_menus.cpp b/core/smn_menus.cpp new file mode 100644 index 00000000..b0c6b17b --- /dev/null +++ b/core/smn_menus.cpp @@ -0,0 +1,890 @@ +/** + * vim: set ts=4 : + * =============================================================== + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * =============================================================== + * + * This file is not open source and may not be copied without explicit + * written permission of AlliedModders LLC. This file may not be redistributed + * in whole or significant part. + * For information, see LICENSE.txt or http://www.sourcemod.net/license.php + * + * Version: $Id$ + */ + +#include "sm_globals.h" +#include +#include "MenuManager.h" +#include "MenuStyle_Valve.h" +#include "MenuStyle_Radio.h" +#include "HandleSys.h" +#include "PluginSys.h" + +#if defined CreateMenu +#undef CreateMenu +#endif + +/** + * And God said, "let there be menus," and behold, there were menus. + * God saw the menus and they were good. And the evening and the morning + * were the third day. + */ + +enum MenuAction +{ + MenuAction_Start = (1<<0), /**< A menu has been started (nothing passed) */ + MenuAction_Display = (1<<1), /**< A menu is about to be displayed (param1=client, param2=MenuPanel Handle) */ + MenuAction_Select = (1<<2), /**< An item was selected (param1=client, param2=item) */ + MenuAction_Cancel = (1<<3), /**< The menu was cancelled (param1=client, param2=item) */ + MenuAction_End = (1<<4), /**< A menu's display/selection cycle is complete (nothing passed). */ + MenuAction_VoteEnd = (1<<5), /**< (VOTE ONLY): A vote sequence has ended (param1=chosen item) */ +}; + +class CPanelHandler : public IMenuHandler +{ + friend class MenuNativeHelpers; +public: + CPanelHandler() + { + } + void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); + void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); +private: + IPluginFunction *m_pFunc; + IPlugin *m_pPlugin; +}; + +class CMenuHandler : public IMenuHandler +{ + friend class MenuNativeHelpers; +public: + CMenuHandler(IPluginFunction *pBasic, int flags); +public: + void OnMenuStart(IBaseMenu *menu); + void OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display); + void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); + void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); + void OnMenuEnd(IBaseMenu *menu); + void OnMenuDestroy(IBaseMenu *menu); +#if 0 + void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style); + void OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display); +#endif +private: + IPluginFunction *m_pBasic; + int m_Flags; +}; + +/** + * GLOBAL CLASS FOR HELPERS + */ + +class MenuNativeHelpers : + public SMGlobalClass, + public IHandleTypeDispatch, + public IPluginsListener +{ +public: + virtual void OnSourceModAllInitialized() + { + m_PanelType = g_HandleSys.CreateType("IMenuPanel", this, 0, NULL, NULL, g_pCoreIdent, NULL); + g_PluginSys.AddPluginsListener(this); + } + + virtual void OnSourceModShutdown() + { + g_HandleSys.RemoveType(m_PanelType, g_pCoreIdent); + + while (!m_FreePanelHandlers.empty()) + { + delete m_FreePanelHandlers.front(); + m_FreePanelHandlers.pop(); + } + + while (!m_FreeMenuHandlers.empty()) + { + delete m_FreeMenuHandlers.front(); + m_FreeMenuHandlers.pop(); + } + } + + virtual void OnHandleDestroy(HandleType_t type, void *object) + { + IMenuPanel *panel = (IMenuPanel *)object; + panel->DeleteThis(); + } + + /** + * It is extremely important that unloaded plugins don't crash. + * Thus, if a plugin unloads, we run through every handler we have. + * This means we do almost no runtime work for keeping track of + * our panel handlers (we don't have to store a list of the running + * ones), but when push comes to shove, we have to scan them all + * in case any of them are active. + */ + virtual void OnPluginUnloaded(IPlugin *plugin) + { + IPluginContext *pContext = plugin->GetBaseContext(); + for (size_t i = 0; i < m_PanelHandlers.size(); i++) + { + if (m_PanelHandlers[i]->m_pPlugin == plugin) + { + m_PanelHandlers[i]->m_pPlugin = NULL; + m_PanelHandlers[i]->m_pFunc = NULL; + } + } + } + + inline HandleType_t GetPanelType() + { + return m_PanelType; + } + + CPanelHandler *GetPanelHandler(IPluginFunction *pFunction) + { + CPanelHandler *handler; + if (m_FreePanelHandlers.empty()) + { + handler = new CPanelHandler; + m_PanelHandlers.push_back(handler); + } else { + handler = m_FreePanelHandlers.front(); + m_FreePanelHandlers.pop(); + } + handler->m_pFunc = pFunction; + handler->m_pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext()); + return handler; + } + + void FreePanelHandler(CPanelHandler *handler) + { + handler->m_pFunc = NULL; + handler->m_pPlugin = NULL; + m_FreePanelHandlers.push(handler); + } + + CMenuHandler *GetMenuHandler(IPluginFunction *pFunction, int flags) + { + CMenuHandler *handler; + if (m_FreeMenuHandlers.empty()) + { + handler = new CMenuHandler(pFunction, flags); + } else { + handler = m_FreeMenuHandlers.front(); + m_FreeMenuHandlers.pop(); + handler->m_pBasic = pFunction; + handler->m_Flags = flags; + } + return handler; + } + + void FreeMenuHandler(CMenuHandler *handler) + { + m_FreeMenuHandlers.push(handler); + } + +private: + HandleType_t m_PanelType; + CStack m_FreePanelHandlers; + CStack m_FreeMenuHandlers; + CVector m_PanelHandlers; +} g_MenuHelpers; + +/** + * BASIC PANEL HANDLER WRAPPER + */ + +void CPanelHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) +{ + if (m_pFunc) + { + m_pFunc->PushCell(BAD_HANDLE); + m_pFunc->PushCell(MenuAction_Cancel); + m_pFunc->PushCell(client); + m_pFunc->PushCell(reason); + m_pFunc->Execute(NULL); + } + g_MenuHelpers.FreePanelHandler(this); +} + +void CPanelHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) +{ + if (m_pFunc) + { + m_pFunc->PushCell(BAD_HANDLE); + m_pFunc->PushCell(MenuAction_Select); + m_pFunc->PushCell(client); + m_pFunc->PushCell(item); + m_pFunc->Execute(NULL); + } + g_MenuHelpers.FreePanelHandler(this); +} + +/** + * MENU HANDLER WRAPPER + */ +CMenuHandler::CMenuHandler(IPluginFunction *pBasic, int flags) : + m_pBasic(pBasic), m_Flags(flags) +{ + /* :TODO: We can probably cache the handle ahead of time */ +} + +void CMenuHandler::OnMenuStart(IBaseMenu *menu) +{ + if ((m_Flags & (int)MenuAction_Start) == (int)MenuAction_Start) + { + m_pBasic->PushCell(menu->GetHandle()); + m_pBasic->PushCell(MenuAction_Start); + m_pBasic->PushCell(0); + m_pBasic->PushCell(0); + m_pBasic->Execute(NULL); + } +} + +void CMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *panel) +{ + if ((m_Flags & (int)MenuAction_Display) == (int)MenuAction_Display) + { + HandleSecurity sec; + sec.pIdentity = m_pBasic->GetParentContext()->GetIdentity(); + sec.pOwner = g_pCoreIdent; + + HandleAccess access; + g_HandleSys.InitAccessDefaults(NULL, &access); + access.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY|HANDLE_RESTRICT_OWNER; + + Handle_t hndl = g_HandleSys.CreateHandleEx(g_MenuHelpers.GetPanelType(), panel, &sec, &access, NULL); + + m_pBasic->PushCell(menu->GetHandle()); + m_pBasic->PushCell(MenuAction_Display); + m_pBasic->PushCell(client); + m_pBasic->PushCell(hndl); + m_pBasic->Execute(NULL); + + g_HandleSys.FreeHandle(hndl, &sec); + } +} + +void CMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) +{ + m_pBasic->PushCell(menu->GetHandle()); + m_pBasic->PushCell(MenuAction_Select); + m_pBasic->PushCell(client); + m_pBasic->PushCell(item); + m_pBasic->Execute(NULL); +} + +void CMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) +{ + m_pBasic->PushCell(menu->GetHandle()); + m_pBasic->PushCell(MenuAction_Cancel); + m_pBasic->PushCell(client); + m_pBasic->PushCell(reason); + m_pBasic->Execute(NULL); +} + +void CMenuHandler::OnMenuEnd(IBaseMenu *menu) +{ + m_pBasic->PushCell(menu->GetHandle()); + m_pBasic->PushCell(MenuAction_End); + m_pBasic->PushCell(0); + m_pBasic->PushCell(0); + m_pBasic->Execute(NULL); +} + +void CMenuHandler::OnMenuDestroy(IBaseMenu *menu) +{ + g_MenuHelpers.FreeMenuHandler(this); +} + +/** + * INLINE FUNCTIONS FOR NATIVES + */ + +inline Handle_t MakePanelHandle(IMenuPanel *panel, IPluginContext *pContext) +{ + return g_HandleSys.CreateHandle(g_MenuHelpers.GetPanelType(), panel, pContext->GetIdentity(), g_pCoreIdent, NULL); +} + +inline HandleError ReadPanelHandle(Handle_t hndl, IMenuPanel **panel) +{ + HandleSecurity sec; + sec.pIdentity = g_pCoreIdent; + sec.pOwner = NULL; + return g_HandleSys.ReadHandle(hndl, g_MenuHelpers.GetPanelType(), &sec, (void **)panel); +} + +inline IMenuStyle *GetStyleFromCell(cell_t cell) +{ + enum MenuStyle + { + MenuStyle_Default = 0, /**< The "default" menu style for the mod */ + MenuStyle_Valve = 1, /**< The Valve provided menu style (Used on HL2DM) */ + MenuStyle_Radio = 2, /**< The simpler menu style commonly used on CS:S */ + }; + + if (cell == MenuStyle_Valve) + { + return &g_ValveMenuStyle; + } else if (cell == MenuStyle_Radio + && g_RadioMenuStyle.IsSupported()) + { + return &g_RadioMenuStyle; + } + + return g_Menus.GetDefaultStyle(); +} + +/*********************************** + **** NATIVE DEFINITIONS *********** + ***********************************/ + +static cell_t CreateMenu(IPluginContext *pContext, const cell_t *params) +{ + IMenuStyle *style = g_Menus.GetDefaultStyle(); + IPluginFunction *pFunction; + + if ((pFunction=pContext->GetFunctionById(params[1])) == NULL) + { + return pContext->ThrowNativeError("Function id %x is invalid", params[1]); + } + + CMenuHandler *handler = g_MenuHelpers.GetMenuHandler(pFunction, params[2]); + IBaseMenu *menu = style->CreateMenu(handler, pContext->GetIdentity()); + + Handle_t hndl = menu->GetHandle(); + if (!hndl) + { + menu->Destroy(); + return BAD_HANDLE; + } + + return hndl; +} + +static cell_t DisplayMenu(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->Display(params[2], params[3]) ? 1 : 0; +} + +static cell_t AddMenuItem(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + char *info; + ItemDrawInfo dr; + + pContext->LocalToString(params[2], &info); + pContext->LocalToString(params[3], (char **)&dr.display); + dr.style = params[4]; + + return menu->AppendItem(info, dr) ? 1 : 0; +} + +static cell_t InsertMenuItem(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + char *info; + ItemDrawInfo dr; + + pContext->LocalToString(params[3], &info); + pContext->LocalToString(params[4], (char **)&dr.display); + dr.style = params[5]; + + return menu->InsertItem(params[2], info, dr) ? 1 : 0; +} + +static cell_t RemoveMenuItem(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->RemoveItem(params[2]) ? 1 : 0; +} + +static cell_t RemoveAllMenuItems(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + menu->RemoveAllItems(); + + return 1; +} + +static cell_t GetMenuItem(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + ItemDrawInfo dr; + const char *info; + + if ((info=menu->GetItemInfo(params[2], &dr)) == NULL) + { + return 0; + } + + pContext->StringToLocalUTF8(params[3], params[4], info, NULL); + pContext->StringToLocalUTF8(params[6], params[7], dr.display ? dr.display : "", NULL); + + cell_t *addr; + pContext->LocalToPhysAddr(params[5], &addr); + *addr = dr.style; + + return 1; +} + +static cell_t SetMenuPagination(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->SetPagination(params[2]) ? 1 : 0; +} + +static cell_t GetMenuPagination(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->GetPagination(); +} + +static cell_t GetMenuItemCount(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->GetItemCount(); +} + +static cell_t SetMenuTitle(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + char buffer[1024]; + g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); + + menu->SetDefaultTitle(buffer); + + return 1; +} + +static cell_t CreatePanelFromMenu(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + IMenuPanel *panel = menu->CreatePanel(); + hndl = MakePanelHandle(panel, pContext); + if (!hndl) + { + panel->DeleteThis(); + } + + return hndl; +} + +static cell_t GetMenuExitButton(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->GetExitButton() ? 1 : 0; +} + +static cell_t SetMenuExitButton(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->SetExitButton(params[2] ? true : false) ? 1 : 0; +} + +static cell_t CancelMenu(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + menu->Cancel(); + + return 1; +} + +static cell_t GetMenuStyle(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IBaseMenu *menu; + + if ((err=g_Menus.ReadMenuHandle(params[1], &menu)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return menu->GetDrawStyle()->GetHandle(); +} + +static cell_t GetMenuStyleHandle(IPluginContext *pContext, const cell_t *params) +{ + IMenuStyle *style = GetStyleFromCell(params[1]); + if (!style) + { + return BAD_HANDLE; + } + + return style->GetHandle(); +} + +static cell_t CreatePanel(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuStyle *style; + + if (hndl != 0) + { + if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None) + { + return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err); + } + } else { + style = g_Menus.GetDefaultStyle(); + } + + IMenuPanel *panel = style->CreatePanel(); + + hndl = MakePanelHandle(panel, pContext); + if (!hndl) + { + panel->DeleteThis(); + return BAD_HANDLE; + } + + return hndl; +} + +static cell_t CreateMenuEx(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuStyle *style; + + if (hndl != 0) + { + if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None) + { + return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err); + } + } else { + style = g_Menus.GetDefaultStyle(); + } + + IPluginFunction *pFunction; + if ((pFunction=pContext->GetFunctionById(params[2])) == NULL) + { + return pContext->ThrowNativeError("Function id %x is invalid", params[2]); + } + + CMenuHandler *handler = g_MenuHelpers.GetMenuHandler(pFunction, params[3]); + + IBaseMenu *pMenu = style->CreateMenu(handler, pContext->GetIdentity()); + hndl = pMenu->GetHandle(); + if (!hndl) + { + pMenu->Destroy(); + return BAD_HANDLE; + } + + return hndl; +} + +static cell_t GetClientMenu(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[2]; + HandleError err; + IMenuStyle *style; + + if (hndl != 0) + { + if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None) + { + return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err); + } + } else { + style = g_Menus.GetDefaultStyle(); + } + + return style->GetClientMenu(params[1], NULL); +} + +static cell_t CancelClientMenu(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[3]; + HandleError err; + IMenuStyle *style; + + if (hndl != 0) + { + if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None) + { + return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err); + } + } else { + style = g_Menus.GetDefaultStyle(); + } + + return style->CancelClientMenu(params[1], params[2] ? true : false) ? 1 : 0; +} + +static cell_t GetMaxPageItems(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuStyle *style; + + if (hndl != 0) + { + if ((err=g_Menus.ReadStyleHandle(params[1], &style)) != HandleError_None) + { + return pContext->ThrowNativeError("MenuStyle handle %x is invalid (error %d)", hndl, err); + } + } else { + style = g_Menus.GetDefaultStyle(); + } + + return style->GetMaxPageItems(); +} + +static cell_t GetPanelStyle(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuPanel *panel; + + if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return panel->GetParentStyle()->GetHandle(); +} + +static cell_t SetPanelTitle(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuPanel *panel; + + if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + char *text; + pContext->LocalToString(params[2], &text); + + panel->DrawTitle(text, params[3] ? true : false); + + return 1; +} + +static cell_t DrawPanelItem(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuPanel *panel; + + if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + ItemDrawInfo dr; + pContext->LocalToString(params[2], (char **)&dr.display); + dr.style = params[3]; + + return panel->DrawItem(dr); +} + +static cell_t DrawPanelText(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuPanel *panel; + + if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + char *text; + pContext->LocalToString(params[2], &text); + + return panel->DrawRawLine(text) ? 1 : 0; +} + +static cell_t CanPanelDrawFlags(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuPanel *panel; + + if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + return panel->CanDrawItem(params[2]); +} + +static cell_t SendPanelToClient(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl = (Handle_t)params[1]; + HandleError err; + IMenuPanel *panel; + + if ((err=ReadPanelHandle(hndl, &panel)) != HandleError_None) + { + return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); + } + + IPluginFunction *pFunction; + if ((pFunction=pContext->GetFunctionById(params[3])) == NULL) + { + return pContext->ThrowNativeError("Function id %x is invalid", params[3]); + } + + CPanelHandler *handler = g_MenuHelpers.GetPanelHandler(pFunction); + if (!panel->SendDisplay(params[2], handler, params[4])) + { + g_MenuHelpers.FreePanelHandler(handler); + } + + return 1; +} + +REGISTER_NATIVES(menuNatives) +{ + {"AddMenuItem", AddMenuItem}, + {"CanPanelDrawFlags", CanPanelDrawFlags}, + {"CancelClientMenu", CancelClientMenu}, + {"CancelMenu", CancelMenu}, + {"CreateMenu", CreateMenu}, + {"CreateMenuEx", CreateMenuEx}, + {"CreatePanel", CreatePanel}, + {"CreatePanelFromMenu", CreatePanelFromMenu},\ + {"DisplayMenu", DisplayMenu}, + {"DrawPanelItem", DrawPanelItem}, + {"DrawPanelText", DrawPanelText}, + {"GetClientMenu", GetClientMenu}, + {"GetMaxPageItems", GetMaxPageItems}, + {"GetMenuExitButton", GetMenuExitButton}, + {"GetMenuItem", GetMenuItem}, + {"GetMenuItemCount", GetMenuItemCount}, + {"GetMenuPagination", GetMenuPagination}, + {"GetMenuStyle", GetMenuStyle}, + {"GetMenuStyleHandle", GetMenuStyleHandle}, + {"GetPanelStyle", GetPanelStyle}, + {"InsertMenuItem", InsertMenuItem}, + {"RemoveAllMenuItems", RemoveAllMenuItems}, + {"RemoveMenuItem", RemoveMenuItem}, + {"SendPanelToClient", SendPanelToClient}, + {"SetMenuExitButton", SetMenuExitButton}, + {"SetMenuPagination", SetMenuPagination}, + {"SetMenuTitle", SetMenuTitle}, + {"SetPanelTitle", SetPanelTitle}, + {NULL, NULL}, +}; diff --git a/core/vm/sp_vm_basecontext.cpp b/core/vm/sp_vm_basecontext.cpp index 6475241e..dbd4f07a 100644 --- a/core/vm/sp_vm_basecontext.cpp +++ b/core/vm/sp_vm_basecontext.cpp @@ -194,6 +194,13 @@ int BaseContext::Execute(uint32_t code_addr, cell_t *result) return SP_ERROR_NOT_RUNNABLE; } + /* tada, prevent a crash */ + cell_t _ignore_result; + if (!result) + { + result = &_ignore_result; + } + IVirtualMachine *vm = (IVirtualMachine *)ctx->vmbase; uint32_t pushcount = ctx->pushcount; diff --git a/plugins/include/menus.inc b/plugins/include/menus.inc new file mode 100644 index 00000000..82ad88bf --- /dev/null +++ b/plugins/include/menus.inc @@ -0,0 +1,500 @@ +/** + * vim: set ts=4 : + * =============================================================== + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * =============================================================== + * + * This file is part of the SourceMod/SourcePawn SDK. This file may only be used + * or modified under the Terms and Conditions of its License Agreement, which is found + * in LICENSE.txt. The Terms and Conditions for making SourceMod extensions/plugins + * may change at any time. To view the latest information, see: + * http://www.sourcemod.net/license.php + * + * Version: $Id$ + */ + +#if defined _menus_included + #endinput +#endif +#define _menus_included + +/** + * Low-level drawing style of the menu. + */ +enum MenuStyle +{ + MenuStyle_Default = 0, /**< The "default" menu style for the mod */ + MenuStyle_Valve = 1, /**< The Valve provided menu style (Used on HL2DM) */ + MenuStyle_Radio = 2, /**< The simpler menu style commonly used on CS:S */ +}; + +/** + * Different actions for the menu "pump" callback + */ +enum MenuAction +{ + MenuAction_Start = (1<<0), /**< A menu has been started (nothing passed) */ + MenuAction_Display = (1<<1), /**< A menu is about to be displayed (param1=client, param2=MenuPanel Handle) */ + MenuAction_Select = (1<<2), /**< An item was selected (param1=client, param2=item) */ + MenuAction_Cancel = (1<<3), /**< The menu was cancelled (param1=client, param2=item) */ + MenuAction_End = (1<<4), /**< A menu's display/selection cycle is complete (nothing passed). */ + MenuAction_VoteEnd = (1<<5), /**< (VOTE ONLY): A vote sequence has ended (param1=chosen item) */ +}; + +/** Default menu actions */ +#define MENU_ACTIONS_DEFAULT MenuAction_Select|MenuAction_Cancel|MenuAction_End +/** All menu actions */ +#define MENU_ACTIONS_ALL -1 + +#define MENU_NO_PAGINATION 0 /**< Menu should not be paginated (10 items max) */ +#define MENU_TIME_FOREVER 0 /**< Menu should be displayed as long as possible */ + +#define ITEMDRAW_DEFAULT (0) /**< Item should be drawn normally */ +#define ITEMDRAW_DISABLED (1<<0) /**< Item is drawn but not selectable */ +#define ITEMDRAW_RAWLINE (1<<1) /**< Item should be a raw line, without a slot */ +#define ITEMDRAW_NOTEXT (1<<2) /**< No text should be drawn */ +#define ITEMDRAW_SPACER (1<<3) /**< Item should be drawn as a spacer, if possible */ +#define ITEMDRAW_IGNORE ((1<<1)|(1<<2)) /**< Item should be completely ignored (rawline + notext) */ +#define ITEMDRAW_CONTROL (1<<4) /**< Item is control text (back/next/exit) */ + +/** + * Reasons a menu can be cancelled. + */ +enum +{ + MenuCancel_Disconnect = -1, /**< Client dropped from the server */ + MenuCancel_Interrupt = -2, /**< Client was interrupted with another menu */ + MenuCancel_Exit = -3, /**< Client selected "exit" on a paginated menu */ + MenuCancel_NoDisplay = -4, /**< Menu could not be displayed to the client */ +}; + +/** + * Describes a menu's source + */ +enum MenuSource +{ + MenuSource_None = 0, /**< No menu is being displayed */ + MenuSource_External = 1, /**< External menu */ + MenuSource_Normal = 2, /**< A basic menu is being displayed */ + MenuSource_RawPanel = 3, /**< A display is active, but it is not tied to a menu */ +}; + +/** + * Called when a menu action is completed. + * + * @param menu The menu being acted upon. + * @param action The action of the menu. + * @param param1 First action parameter (usually the client). + * @param param2 Second action parameter (usually the item). + * @noreturn + */ +functag MenuHandler public(Handle:menu, MenuAction:action, param1, param2); + +/** + * Creates a new, empty menu using the default style. + * + * @param handler Function which will receive menu actions. + * @param actions Optionally set which actions to receive. Select, + * Cancel, and End will always be received regardless + * of whether they are set or not. They are also + * the only default actions. + * @return A new menu Handle. + */ +native Handle:CreateMenu(MenuHandler:handler, MenuAction:actions=MENU_ACTIONS_DEFAULT); + +/** + * Displays a menu to a client. + * + * @param menu Menu Handle. + * @param client Client index. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false on failure. + * @error Invalid Handle or client not in game. + */ +native bool:DisplayMenu(Handle:menu, client, time); + +/** + * Appends a new item to the end of a menu. + * + * @param menu Menu Handle. + * @param info Item information string. + * @param display Default item display string. + * @param style Drawing style flags. + * @return True on success, false on failure. + * @error Invalid Handle or item limit reached. + */ +native AddMenuItem(Handle:menu, + const String:info[], + const String:display[], + style=ITEMDRAW_DEFAULT); + +/** + * Inserts an item into the menu before a certain position; the new item will + * be at the given position and all next items pushed forward. + * + * @param menu Menu Handle. + * @param position Position, starting from 0. + * @param info Item information string. + * @param display Default item display string. + * @param style Drawing style flags. + * @return True on success, false on failure. + * @error Invalid Handle or menu position. + */ +native bool:InsertMenuItem(Handle:menu, + position, + const String:info[], + const String:display[], + style=ITEMDRAW_DEFAULT); + +/** + * Removes an item from the menu. + * + * @param menu Menu Handle. + * @param position Position, starting from 0. + * @return True on success, false on failure. + * @error Invalid Handle or menu position. + */ +native bool:RemoveMenuItem(Handle:menu, position); + +/** + * Removes all items from a menu. + * + * @param menu Menu Handle. + * @noreturn + * @error Invalid Handle or menu position. + */ +native RemoveAllMenuItems(Handle:menu); + +/** + * Retrieves information about a menu item. + * + * @param menu Menu Handle. + * @param position Position, starting from 0. + * @param infoBuf Info buffer. + * @param infoBufLen Maximum length of the info buffer. + * @param style By-reference variable to store drawing flags. + * @param dispBuf Display buffer. + * @param dispBufLen Maximum length of the display buffer. + * @return True on success, false if position is invalid. + * @error Invalid Handle. + */ +native bool:GetMenuItem(Handle:menu, position, String:infoBuf[], infoBufLen, &style=0, String:dispBuf[]="", dispBufLen=0); + +/** + * Returns the number of items in a menu. + * + * @param menu Menu Handle. + * @return Number of items in the menu. + * @error Invalid Handle. + */ +native GetMenuItemCount(Handle:menu); + +/** + * Sets whether the menu should be paginated or not. + * + * @param menu Handle to the menu. + * @param itemsPerPage Number of items per page, or MENU_NO_PAGINATION. + * @return True on success, false if pagination is too high or low. + * @error Invalid Handle. + */ +native bool:SetMenuPagination(Handle:menu, itemsPerPage); + +/** + * Returns a menu's pagination setting. + * + * @param menu Handle to the menu. + * @return Pagination setting. + * @error Invalid Handle. + */ +native GetMenuPagination(Handle:menu); + +/** + * Returns a menu's MenuStyle Handle. The Handle + * is global and cannot be freed. + * + * @param menu Handle to the menu. + * @return Handle to the menu's draw style. + * @error Invalid Handle. + */ +native Handle:GetMenuStyle(Handle:menu); + +/** + * Sets the menu's default title/instruction message. + * + * @param menu Menu Handle. + * @param fmt Message string format + * @param ... Message string arguments. + * @noreturn + * @error Invalid Handle. + */ +native SetMenuTitle(Handle:menu, const String:fmt[], any:...); + +/** + * Creates a raw MenuPanel based off the menu's style. + * The Handle must be freed with CloseHandle(). + * + * @return A new MenuPanel Handle. + * @error Invalid Handle. + */ +native Handle:CreatePanelFromMenu(Handle:menu); + +/** + * Returns whether or not the menu has an exit button. + * By default, menus have an exit button. + * + * @param menu Menu Handle. + * @return True if the menu has an exit button; false otherwise. + * @error Invalid Handle. + */ +native bool:GetMenuExitButton(Handle:menu); + +/** + * Sets whether or not the menu has an exit button. + * By default, menus have an exit button. + * + * @param menu Menu Handle. + * @param button True to enable the button, false to remove it. + * @return True if allowed; false on failure. + * @error Invalid Handle. + */ +native bool:SetMenuExitButton(Handle:menu, bool:button); + +/** + * Cancels a menu from displaying on all clients. While the + * cancellation is in progress, this menu cannot be re-displayed + * to any clients. + * + * The menu may still exist on the client's screen after this command. + * This simply verifies that the menu is not being used anywhere. + * + * @param menu Menu Handle. + * @noreturn + * @error Invalid Handle. + */ +native CancelMenu(Handle:menu); + +#if 0 +/** + * Broadcasts a menu to a list of clients. + * + * @param menu Menu Handle. + * @param handler MenuHandler callback to receive actions. + * @param players Array of players to broadcast to. + * @param numPlayers Number of players in the array. + * @param time Maximum time to leave menu on the screen. + * @return Number of clients that broadcast will wait upon. + * @error Invalid Handle. + */ +native BroadcastMenu(Handle:menu, MenuHandler:handler, players[], numPlayers, time); + +/** + * Broadcasts a menu to a list of clients. The most selected + * item will be returned through MenuAction_End. On a tie, a random + * item will be returned. + * + * @param menu Menu Handle. + * @param handler MenuHandler callback to receive actions. + * @param players Array of players to broadcast to. + * @param numPlayers Number of players in the array. + * @param time Maximum time to leave menu on the screen. + * @return Number of clients that vote will wait upon. + * @error Invalid Handle. + */ +native VoteMenu(Handle:menu, MenuHandler:handler, players[], numPlayers, time); + +/** + * Broadcasts a menu to all clients. + * + * @param menu Menu Handle. + * @param handler MenuHandler callback to receive actions. + * @param time Maximum time to leave menu on the screen. + * @return Number of clients that broadcast will wait upon. + * @error Invalid Handle. + */ +stock BroadcastMenuToAll(Handle:menu, MenuHandler:handler, time) +{ + new num = GetMaxClients(); + new total; + decl players[num]; + + for (new i=1; i<=num; i++) + { + if (!IsClientConnected(i)) + { + continue; + } + players[total++] = i; + } + + return BroadcastMenu(menu, handler, players, total, time); +} + +/** + * Broadcasts a menu to all clients. The most selected item will + * be returned through MenuAction_End. On a tie, a random item + * will be returned. + * + * @param menu Menu Handle. + * @param handler MenuHandler callback to receive actions. + * @param time Maximum time to leave menu on the screen. + * @return Number of clients that the vote will wait upon. + * @error Invalid Handle. + */ +native VoteMenuToAll(Handle:menu, MenuHandler:handler, time) +{ + new num = GetMaxClients(); + new total; + decl players[num]; + + for (new i=1; i<=num; i++) + { + if (!IsClientConnected(i)) + { + continue; + } + players[total++] = i; + } + + return VoteMenu(menu, handler, players, total, time); +} +#endif + +/** + * Returns a style's global Handle. + * + * @param style Menu Style. + * @return A Handle, or INVALID_HANDLE if not found or unusable. + */ +native Handle:GetMenuStyleHandle(MenuStyle:style); + +/** + * Creates a MenuPanel from a MenuStyle. Panels are used for drawing raw + * menus without any extra helper functions. The Handle must be closed + * with CloseHandle(). + * + * @param hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @return A new MenuPanel Handle. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native Handle:CreatePanel(Handle:hStyle=INVALID_HANDLE); + +/** + * Creates a Menu from a MenuStyle. The Handle must be closed with + * CloseHandle(). + * + * @parma hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @param handler Function which will receive menu actions. + * @param actions Optionally set which actions to receive. Select, + * Cancel, and End will always be received regardless + * of whether they are set or not. They are also + * the only default actions. + * @return A new menu Handle. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native Handle:CreateMenuEx(Handle:hStyle=INVALID_HANDLE, MenuHandler:handler, MenuAction:actions=MENU_ACTIONS_DEFAULT); + +/** + * Returns whether a client is viewing a menu. If the menu source + * is MenuSource_Normal, a menu Handle will also be returned. + * + * @param client Client index. + * @param hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @return A MenuSource value. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native MenuSource:GetClientMenu(client, Handle:hStyle=INVALID_HANDLE); + +/** + * Cancels a menu on a client. This will only affect non-external menus. + * + * @param hstyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @param client Client index. + * @param autoIgnore If true, no menus can be re-drawn on the client during + * the cancellation process. + * @return True if a menu was cancelled, false otherwise. + */ +native bool:CancelClientMenu(client, bool:autoIgnore=false, Handle:hStyle=INVALID_HANDLE); + +/** + * Returns a style's maximum items per page. + * + * @param hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @return Maximum items per page. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native GetMaxPageItems(Handle:hStyle=INVALID_HANDLE); + +/** + * Returns a MenuPanel's parent style. + * + * @param panel A MenuPanel Handle. + * @return The MenuStyle Handle that created the panel. + * @error Invalid Handle. + */ +native Handle:GetPanelStyle(Handle:panel); + +/** + * Sets the panel's title. + * + * @param panel A MenuPanel Handle. + * @param title Text to set as the title. + * @param onlyIfEmpty If true, the title will only be set if no title is set. + * @noreturn + * @error Invalid Handle. + */ +native Handle:SetPanelTitle(Handle:panel, const String:text[], bool:onlyIfEmpty=false); + +/** + * Draws an item on a panel. If the item takes up a slot, the position + * is returned. + * + * @param panel A MenuPanel Handle. + * @param text Display text to use. If not a raw line, + * the style may automatically add color markup. + * No numbering or newlines are needed. + * @param style ITEMDRAW style flags. + * @return A slot position, or 0 if item was a rawline or could not be drawn. + * @error Invalid Handle. + */ +native DrawPanelItem(Handle:panel, const String:text[], style=ITEMDRAW_DEFAULT); + +/** + * Draws a raw line of text on a panel, without any markup other than a newline. + * + * @param panel A MenuPanel Handle. + * @param text Display text to use. + * @return True on success, false if raw lines are not supported. + * @error Invalid Handle. + */ +native DrawPanelText(Handle:panel, const String:text[]); + +/** + * Returns whether or not the given drawing flags are supported by + * the menu style. + * + * @param panel A MenuPanel Handle. + * @param style ITEMDRAW style flags. + * @return True if item is drawable, false otherwise. + * @error Invalid Handle. + */ +native CanPanelDrawFlags(Handle:panel, style); + +/** + * Sends a panel to a client. Unlike full menus, the handler + * function will only receive the following actions, both of + * which will have INVALID_HANDLE for a menu, and the client + * as param1. + * + * MenuAction_Select (param2 will be the key pressed) + * MenuAction_Cancel (param2 will be the reason) + * + * Also, if the menu fails to display, no callbacks will be called. + * + * @param panel A MenuPanel Handle. + * @param client A client to draw to. + * @param handler The MenuHandler function to catch actions with. + * @param time Time to hold the menu for. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:SendPanelToClient(Handle:panel, client, MenuHandler:handler, time); diff --git a/public/IMenuManager.h b/public/IMenuManager.h index 2daf5970..77a05389 100644 --- a/public/IMenuManager.h +++ b/public/IMenuManager.h @@ -20,6 +20,7 @@ #define _INCLUDE_SOURCEMOD_MENU_SYSTEM_H_ #include +#include #define SMINTERFACE_MENUMANAGER_NAME "IMenuManager" #define SMINTERFACE_MENUMANAGER_VERSION 1 @@ -263,9 +264,12 @@ namespace SourceMod * * Note: the object should be freed using IBaseMenu::Destroy. * + * @param handler IMenuHandler pointer. + * @param pOwner Optional IdentityToken_t owner for handle + * creation. * @return An IBaseMenu pointer. */ - virtual IBaseMenu *CreateMenu() =0; + virtual IBaseMenu *CreateMenu(IMenuHandler *handler, IdentityToken_t *pOwner=NULL) =0; /** * @brief Returns the maximum number of items per page. @@ -293,6 +297,13 @@ namespace SourceMod * @return True if a menu was cancelled, false otherwise. */ virtual bool CancelClientMenu(int client, bool autoIgnore=false) =0; + + /** + * @brief Returns a Handle the IMenuStyle object. + * + * @return Handle_t pointing to this object. + */ + virtual Handle_t GetHandle() =0; }; /** @@ -428,16 +439,20 @@ namespace SourceMod * @brief Sends the menu to a client. * * @param client Client index to display to. - * @param handler Menu handler to use. * @param time Time to hold menu for. * @return True on success, false otherwise. */ - virtual bool Display(int client, IMenuHandler *handler, unsigned int time) =0; + virtual bool Display(int client, unsigned int time) =0; /** - * @brief Destroys the menu and frees all associated resources.1 + * @brief Destroys the menu and frees all associated resources. + * + * @param releaseHandle If true, the Handle will be released + * in the destructor. This should be set + * to true except for IHandleTypeDispatch + * destructors. */ - virtual void Destroy() =0; + virtual void Destroy(bool releaseHandle=true) =0; /** * @brief Cancels the menu on all client's displays. While the menu is @@ -446,6 +461,14 @@ namespace SourceMod * @return Number of menus cancelled. */ virtual void Cancel() =0; + + /** + * @brief Returns the menu's Handle. The Handle is automatically + * removed when the menu is destroyed. + * + * @return Handle_t handle value. + */ + virtual Handle_t GetHandle() =0; }; /** @@ -516,6 +539,15 @@ namespace SourceMod { } + /** + * @brief Called when the menu object is destroyed. + * + * @param menu Menu pointer. + */ + virtual void OnMenuDestroy(IBaseMenu *menu) + { + } + /** * @brief Called when requesting how to render an item. * @@ -579,11 +611,11 @@ 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 handler IMenuHandler pointer. * @param clients Array of client indexes. * @param numClients Number of clients in the array. * @param time Time to hold the menu. @@ -610,6 +642,7 @@ namespace SourceMod int clients[], unsigned int numClients, unsigned int time) =0; +#endif /** * @brief Returns the default draw style Core is using.