/** * vim: set ts=4 : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * * As a special exception, AlliedModders LLC gives you permission to link the * code of this program (as well as its derivative works) to "Half-Life 2," the * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software * by the Valve Corporation. You must obey the GNU General Public License in * all respects for all other code used. Additionally, AlliedModders LLC grants * this exception to all derivative works. AlliedModders LLC defines further * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), * or . * * Version: $Id$ */ #include "sm_globals.h" #include #include "MenuManager.h" #include "MenuStyle_Valve.h" #include "MenuStyle_Radio.h" #include "PlayerManager.h" #include "sm_stringutil.h" #include "sourcemm_api.h" #if defined MENU_DEBUG #include "Logger.h" #endif #include "ChatTriggers.h" #include "logic_bridge.h" #include "sourcemod.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) */ MenuAction_VoteStart = (1<<6), /**< (VOTE ONLY): A vote sequence has started */ MenuAction_VoteCancel = (1<<7), /**< (VOTE ONLY): A vote sequence has been cancelled (nothing passed) */ MenuAction_DrawItem = (1<<8), /**< A style is being drawn; return the new style (param1=client, param2=item) */ MenuAction_DisplayItem = (1<<9), /**< the odd duck */ }; 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 OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page); void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); void OnMenuEnd(IBaseMenu *menu, MenuEndReason reason); void OnMenuDestroy(IBaseMenu *menu); void OnMenuVoteStart(IBaseMenu *menu); void OnMenuVoteResults(IBaseMenu *menu, const menu_vote_result_t *results); void OnMenuVoteCancel(IBaseMenu *menu, VoteCancelReason reason); void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style); unsigned int OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr); bool OnSetHandlerOption(const char *option, const void *data); #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: cell_t DoAction(IBaseMenu *menu, MenuAction action, cell_t param1, cell_t param2, cell_t def_res=0); private: IPluginFunction *m_pBasic; int m_Flags; IPluginFunction *m_pVoteResults; cell_t m_fnVoteResult; }; /** * GLOBAL CLASS FOR HELPERS */ class MenuNativeHelpers : public SMGlobalClass, public IHandleTypeDispatch, public IPluginsListener { public: virtual void OnSourceModAllInitialized() { m_PanelType = handlesys->CreateType("IMenuPanel", this, 0, NULL, NULL, g_pCoreIdent, NULL); m_TempPanelType = handlesys->CreateType("TempIMenuPanel", this, m_PanelType, NULL, NULL, g_pCoreIdent, NULL); scripts->AddPluginsListener(this); } virtual void OnSourceModShutdown() { scripts->RemovePluginsListener(this); handlesys->RemoveType(m_TempPanelType, g_pCoreIdent); 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) { if (type == m_TempPanelType) { return; } IMenuPanel *panel = (IMenuPanel *)object; panel->DeleteThis(); } virtual bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize) { *pSize = ((IMenuPanel *)object)->GetApproxMemUsage(); return true; } /** * 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) { 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; } inline HandleType_t GetTempPanelType() { return m_TempPanelType; } 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 = scripts->FindPluginByContext(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; handler->m_pVoteResults = NULL; } return handler; } void FreeMenuHandler(CMenuHandler *handler) { m_FreeMenuHandlers.push(handler); } private: HandleType_t m_PanelType; HandleType_t m_TempPanelType; 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) { unsigned int old_reply = g_ChatTriggers.SetReplyTo(SM_REPLY_CHAT); m_pFunc->PushCell(BAD_HANDLE); m_pFunc->PushCell(MenuAction_Select); m_pFunc->PushCell(client); m_pFunc->PushCell(item); m_pFunc->Execute(NULL); g_ChatTriggers.SetReplyTo(old_reply); } g_MenuHelpers.FreePanelHandler(this); } static IMenuPanel *s_pCurPanel = NULL; static unsigned int s_CurPanelReturn = 0; static const ItemDrawInfo *s_CurDrawInfo = NULL; static unsigned int *s_CurSelectPosition = NULL; /** * MENU HANDLER WRAPPER */ CMenuHandler::CMenuHandler(IPluginFunction *pBasic, int flags) : m_pBasic(pBasic), m_Flags(flags), m_pVoteResults(NULL) { /* :TODO: We can probably cache the handle ahead of time */ } void CMenuHandler::OnMenuStart(IBaseMenu *menu) { if ((m_Flags & (int)MenuAction_Start) == (int)MenuAction_Start) { DoAction(menu, MenuAction_Start, 0, 0); } } void CMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *panel) { if ((m_Flags & (int)MenuAction_Display) == (int)MenuAction_Display) { HandleSecurity sec; sec.pIdentity = g_pCoreIdent; sec.pOwner = m_pBasic->GetParentContext()->GetIdentity(); HandleAccess access; handlesys->InitAccessDefaults(NULL, &access); access.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY|HANDLE_RESTRICT_OWNER; Handle_t hndl = handlesys->CreateHandleEx(g_MenuHelpers.GetTempPanelType(), panel, &sec, &access, NULL); DoAction(menu, MenuAction_Display, client, hndl); handlesys->FreeHandle(hndl, &sec); } } void CMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page) { /* Save old position first. */ unsigned int first_item = item_on_page; unsigned int *old_pos = s_CurSelectPosition; s_CurSelectPosition = &first_item; unsigned int old_reply = g_ChatTriggers.SetReplyTo(SM_REPLY_CHAT); DoAction(menu, MenuAction_Select, client, item); g_ChatTriggers.SetReplyTo(old_reply); s_CurSelectPosition = old_pos; } void CMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) { unsigned int old_reply = g_ChatTriggers.SetReplyTo(SM_REPLY_CHAT); DoAction(menu, MenuAction_Cancel, client, (cell_t)reason); g_ChatTriggers.SetReplyTo(old_reply); } void CMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) { DoAction(menu, MenuAction_End, reason, 0); } void CMenuHandler::OnMenuDestroy(IBaseMenu *menu) { g_MenuHelpers.FreeMenuHandler(this); } void CMenuHandler::OnMenuVoteStart(IBaseMenu *menu) { DoAction(menu, MenuAction_VoteStart, 0, 0); } void CMenuHandler::OnMenuVoteCancel(IBaseMenu *menu, VoteCancelReason reason) { DoAction(menu, MenuAction_VoteCancel, reason, 0); } void CMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) { if ((m_Flags & (int)MenuAction_DrawItem) == (int)MenuAction_DrawItem) { cell_t result = DoAction(menu, MenuAction_DrawItem, client, item, style); style = (unsigned int)result; } } unsigned int CMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr) { if ((m_Flags & (int)MenuAction_DisplayItem) == (int)MenuAction_DisplayItem) { IMenuPanel *oldpanel = s_pCurPanel; unsigned int oldret = s_CurPanelReturn; const ItemDrawInfo *oldinfo = s_CurDrawInfo; s_pCurPanel = panel; s_CurPanelReturn = 0; s_CurDrawInfo = &dr; cell_t res = DoAction(menu, MenuAction_DisplayItem, client, item, 0); if (!res) { res = s_CurPanelReturn; } s_pCurPanel = oldpanel; s_CurPanelReturn = oldret; s_CurDrawInfo = oldinfo; return res; } return 0; } cell_t CMenuHandler::DoAction(IBaseMenu *menu, MenuAction action, cell_t param1, cell_t param2, cell_t def_res) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] CMenuHandler::DoAction() (menu %p/%08x) (action %d) (param1 %d) (param2 %d)", menu, menu->GetHandle(), action, param1, param2); #endif cell_t res = def_res; m_pBasic->PushCell(menu->GetHandle()); m_pBasic->PushCell((cell_t)action); m_pBasic->PushCell(param1); m_pBasic->PushCell(param2); m_pBasic->Execute(&res); return res; } void CMenuHandler::OnMenuVoteResults(IBaseMenu *menu, const menu_vote_result_t *results) { if (!m_pVoteResults) { /* Call MenuAction_VoteEnd instead. See if there are any extra winners. */ unsigned int num_items = 1; for (unsigned int i=1; inum_items; i++) { if (results->item_list[i].count != results->item_list[0].count) { break; } num_items++; } /* See if we need to pick a random winner. */ unsigned int winning_item; if (num_items > 1) { /* Yes, we do. */ srand(time(NULL)); winning_item = rand() % num_items; winning_item = results->item_list[winning_item].item; } else { /* No, take the first. */ winning_item = results->item_list[0].item; } unsigned int total_votes = results->num_votes; unsigned int winning_votes = results->item_list[0].count; DoAction(menu, MenuAction_VoteEnd, winning_item, (total_votes << 16) | (winning_votes & 0xFFFF)); } else { IPluginContext *pContext = m_pVoteResults->GetParentContext(); bool no_call = false; int err; /* First array */ cell_t client_array_address = -1; cell_t *client_array_base = NULL; cell_t client_array_size = results->num_clients + (results->num_clients * 2); if (client_array_size) { if ((err = pContext->HeapAlloc(client_array_size, &client_array_address, &client_array_base)) != SP_ERROR_NONE) { logicore.GenerateError(pContext, m_fnVoteResult, err, "Menu callback could not allocate %d bytes for client list.", client_array_size * sizeof(cell_t)); no_call = true; } else { cell_t target_offs = sizeof(cell_t) * results->num_clients; cell_t *cur_index = client_array_base; cell_t *cur_array; for (unsigned int i=0; inum_clients; i++) { /* Copy the array index */ *cur_index = target_offs; /* Get the current array address */ cur_array = (cell_t *)((char *)cur_index + target_offs); /* Store information */ cur_array[0] = results->client_list[i].client; cur_array[1] = results->client_list[i].item; /* Adjust for the new target by subtracting one indirection * and adding one array. */ target_offs += (sizeof(cell_t) * 2) - sizeof(cell_t); cur_index++; } } } /* Second array */ cell_t item_array_address = -1; cell_t *item_array_base = NULL; cell_t item_array_size = results->num_items + (results->num_items * 2); if (item_array_size) { if ((err = pContext->HeapAlloc(item_array_size, &item_array_address, &item_array_base)) != SP_ERROR_NONE) { logicore.GenerateError(pContext, m_fnVoteResult, err, "Menu callback could not allocate %d bytes for item list.", item_array_size); no_call = true; } else { cell_t target_offs = sizeof(cell_t) * results->num_items; cell_t *cur_index = item_array_base; cell_t *cur_array; for (unsigned int i=0; inum_items; i++) { /* Copy the array index */ *cur_index = target_offs; /* Get the current array address */ cur_array = (cell_t *)((char *)cur_index + target_offs); /* Store information */ cur_array[0] = results->item_list[i].item; cur_array[1] = results->item_list[i].count; /* Adjust for the new target by subtracting one indirection * and adding one array. */ target_offs += (sizeof(cell_t) * 2) - sizeof(cell_t); cur_index++; } } } /* Finally, push everything */ if (!no_call) { m_pVoteResults->PushCell(menu->GetHandle()); m_pVoteResults->PushCell(results->num_votes); m_pVoteResults->PushCell(results->num_clients); m_pVoteResults->PushCell(client_array_address); m_pVoteResults->PushCell(results->num_items); m_pVoteResults->PushCell(item_array_address); m_pVoteResults->Execute(NULL); } /* Free what we allocated, in reverse order as required */ if (item_array_address != -1) { pContext->HeapPop(item_array_address); } if (client_array_address != -1) { pContext->HeapPop(client_array_address); } } } bool CMenuHandler::OnSetHandlerOption(const char *option, const void *data) { if (strcmp(option, "set_vote_results_handler") == 0) { void **array = (void **)data; m_pVoteResults = (IPluginFunction *)array[0]; m_fnVoteResult = *(cell_t *)((cell_t *)array[1]); return true; } return false; } /** * INLINE FUNCTIONS FOR NATIVES */ inline Handle_t MakePanelHandle(IMenuPanel *panel, IPluginContext *pContext) { return 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 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 DisplayMenuAtItem(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->DisplayAtItem(params[2], params[4], params[3]) ? 1 : 0; } static cell_t VoteMenu(IPluginContext *pContext, const cell_t *params) { if (g_Menus.IsVoteInProgress()) { return pContext->ThrowNativeError("A vote is already in progress"); } 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); } cell_t *addr; pContext->LocalToPhysAddr(params[2], &addr); cell_t flags = 0; if (params[0] >= 5) { flags = params[5]; } if (!g_Menus.StartVote(menu, params[3], addr, params[4], flags)) { return 0; } return 1; } 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); } g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); menu->SetDefaultTitle(buffer); return 1; } static cell_t GetMenuTitle(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); } size_t written; const char *title = menu->GetDefaultTitle(); pContext->StringToLocalUTF8(params[2], params[3], title, &written); return (cell_t)written; } 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->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT) ? 1 : 0; } static cell_t GetMenuExitBackButton(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->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXIT) ? 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); } unsigned int flags = menu->GetMenuOptionFlags(); if (params[2]) { flags |= MENUFLAG_BUTTON_EXIT; } else { flags &= ~MENUFLAG_BUTTON_EXIT; } menu->SetMenuOptionFlags(flags); unsigned int new_flags = menu->GetMenuOptionFlags(); return (flags == new_flags); } static cell_t SetMenuNoVoteButton(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); } unsigned int flags = menu->GetMenuOptionFlags(); if (params[2]) { flags |= MENUFLAG_BUTTON_NOVOTE; } else { flags &= ~MENUFLAG_BUTTON_NOVOTE; } menu->SetMenuOptionFlags(flags); unsigned int new_flags = menu->GetMenuOptionFlags(); return (flags == new_flags); } static cell_t SetMenuExitBackButton(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); } unsigned int flags = menu->GetMenuOptionFlags(); if (params[2]) { flags |= MENUFLAG_BUTTON_EXITBACK; } else { flags &= ~MENUFLAG_BUTTON_EXITBACK; } menu->SetMenuOptionFlags(flags); return 1; } 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); } g_Menus.CancelMenu(menu); return 1; } static cell_t IsVoteInProgress(IPluginContext *pContext, const cell_t *params) { return g_Menus.IsVoteInProgress() ? 1 : 0; } 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; } static cell_t SetPanelKeys(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->SetSelectableKeys(params[2]); } static cell_t GetPanelCurrentKey(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->GetCurrentKey(); } static cell_t GetPanelTextRemaining(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->GetAmountRemaining(); } static cell_t SetPanelCurrentKey(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->SetCurrentKey(params[2]) ? 1 : 0; } static cell_t RedrawMenuItem(IPluginContext *pContext, const cell_t *params) { if (!s_pCurPanel) { return pContext->ThrowNativeError("You can only call this once from a MenuAction_DisplayItem callback"); } char *str; pContext->LocalToString(params[1], &str); ItemDrawInfo dr = *s_CurDrawInfo; dr.display = str; if ((s_CurPanelReturn = s_pCurPanel->DrawItem(dr)) != 0) { s_pCurPanel = NULL; } return s_CurPanelReturn; } static cell_t GetMenuOptionFlags(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->GetMenuOptionFlags(); } static cell_t SetMenuOptionFlags(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->SetMenuOptionFlags(params[2]); return 1; } static cell_t CancelVote(IPluginContext *pContxt, const cell_t *params) { if (!g_Menus.IsVoteInProgress()) { return pContxt->ThrowNativeError("No vote is in progress"); } g_Menus.CancelVoting(); return 1; } static cell_t SetVoteResultCallback(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); } IPluginFunction *pFunction = pContext->GetFunctionById(params[2]); if (!pFunction) { return pContext->ThrowNativeError("Invalid function %x", params[2]); } void *array[2]; array[0] = pFunction; array[1] = (void *)¶ms[2]; IMenuHandler *pHandler = menu->GetHandler(); if (!pHandler->OnSetHandlerOption("set_vote_results_handler", (const void *)array)) { return pContext->ThrowNativeError("The given menu does not support this option"); } return 1; } static cell_t CheckVoteDelay(IPluginContext *pContext, const cell_t *params) { return g_Menus.GetRemainingVoteDelay(); } static cell_t GetMenuSelectionPosition(IPluginContext *pContext, const cell_t *params) { if (!s_CurSelectPosition) { return pContext->ThrowNativeError("Can only be called from inside a MenuAction_Select callback"); } return *s_CurSelectPosition; } static cell_t IsClientInVotePool(IPluginContext *pContext, const cell_t *params) { int client; IGamePlayer *pPlayer; client = params[1]; if ((pPlayer = g_Players.GetPlayerByIndex(client)) == NULL) { return pContext->ThrowNativeError("Invalid client index %d", client); } if (!g_Menus.IsVoteInProgress()) { return pContext->ThrowNativeError("No vote is in progress"); } return g_Menus.IsClientInVotePool(client) ? 1 : 0; } static cell_t RedrawClientVoteMenu(IPluginContext *pContext, const cell_t *params) { int client; IGamePlayer *pPlayer; client = params[1]; if ((pPlayer = g_Players.GetPlayerByIndex(client)) == NULL) { return pContext->ThrowNativeError("Invalid client index %d", client); } if (!g_Menus.IsVoteInProgress()) { return pContext->ThrowNativeError("No vote is in progress"); } if (!g_Menus.IsClientInVotePool(client)) { return pContext->ThrowNativeError("Client is not in the voting pool"); } bool revote = true; if (params[0] >= 2 && !params[2]) { revote = false; } return g_Menus.RedrawClientVoteMenu2(client, revote) ? 1 : 0; } class EmptyMenuHandler : public IMenuHandler { public: } s_EmptyMenuHandler; static cell_t InternalShowMenu(IPluginContext *pContext, const cell_t *params) { int client = params[1]; CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); if (pPlayer == NULL) { return pContext->ThrowNativeError("Invalid client index %d", client); } else if (!pPlayer->IsInGame()) { return pContext->ThrowNativeError("Client %d is not in game", client); } if (!g_RadioMenuStyle.IsSupported()) { return pContext->ThrowNativeError("Radio menus are not supported on this mod"); } char *str; pContext->LocalToString(params[2], &str); IMenuPanel *pPanel = g_RadioMenuStyle.MakeRadioDisplay(str, params[4]); if (pPanel == NULL) { return 0; } IMenuHandler *pHandler; CPanelHandler *pActualHandler = NULL; if (params[5] != -1) { IPluginFunction *pFunction = pContext->GetFunctionById(params[5]); if (pFunction == NULL) { return pContext->ThrowNativeError("Invalid function index %x", params[5]); } pActualHandler = g_MenuHelpers.GetPanelHandler(pFunction); } if (pActualHandler == NULL) { pHandler = &s_EmptyMenuHandler; } else { pHandler = pActualHandler; } bool bSuccess = pPanel->SendDisplay(client, pHandler, params[3]); pPanel->DeleteThis(); if (!bSuccess && pActualHandler != NULL) { g_MenuHelpers.FreePanelHandler(pActualHandler); } return bSuccess ? 1 : 0; } REGISTER_NATIVES(menuNatives) { {"AddMenuItem", AddMenuItem}, {"CanPanelDrawFlags", CanPanelDrawFlags}, {"CancelClientMenu", CancelClientMenu}, {"CancelMenu", CancelMenu}, {"CancelVote", CancelVote}, {"CheckVoteDelay", CheckVoteDelay}, {"CreateMenu", CreateMenu}, {"CreateMenuEx", CreateMenuEx}, {"CreatePanel", CreatePanel}, {"CreatePanelFromMenu", CreatePanelFromMenu}, {"DisplayMenu", DisplayMenu}, {"DisplayMenuAtItem", DisplayMenuAtItem}, {"DrawPanelItem", DrawPanelItem}, {"DrawPanelText", DrawPanelText}, {"GetClientMenu", GetClientMenu}, {"GetMaxPageItems", GetMaxPageItems}, {"GetMenuExitBackButton", GetMenuExitBackButton}, {"GetMenuExitButton", GetMenuExitButton}, {"GetMenuItem", GetMenuItem}, {"GetMenuItemCount", GetMenuItemCount}, {"GetMenuOptionFlags", GetMenuOptionFlags}, {"GetMenuPagination", GetMenuPagination}, {"GetMenuSelectionPosition",GetMenuSelectionPosition}, {"GetMenuStyle", GetMenuStyle}, {"GetMenuStyleHandle", GetMenuStyleHandle}, {"GetMenuTitle", GetMenuTitle}, {"GetPanelTextRemaining", GetPanelTextRemaining}, {"GetPanelCurrentKey", GetPanelCurrentKey}, {"GetPanelStyle", GetPanelStyle}, {"InsertMenuItem", InsertMenuItem}, {"InternalShowMenu", InternalShowMenu}, {"IsClientInVotePool", IsClientInVotePool}, {"IsVoteInProgress", IsVoteInProgress}, {"RedrawClientVoteMenu", RedrawClientVoteMenu}, {"RedrawMenuItem", RedrawMenuItem}, {"RemoveAllMenuItems", RemoveAllMenuItems}, {"RemoveMenuItem", RemoveMenuItem}, {"SendPanelToClient", SendPanelToClient}, {"SetMenuExitBackButton", SetMenuExitBackButton}, {"SetMenuExitButton", SetMenuExitButton}, {"SetMenuOptionFlags", SetMenuOptionFlags}, {"SetMenuPagination", SetMenuPagination}, {"SetMenuTitle", SetMenuTitle}, {"SetPanelCurrentKey", SetPanelCurrentKey}, {"SetPanelTitle", SetPanelTitle}, {"SetPanelKeys", SetPanelKeys}, {"SetVoteResultCallback", SetVoteResultCallback}, {"VoteMenu", VoteMenu}, {"SetMenuNoVoteButton", SetMenuNoVoteButton}, {NULL, NULL}, };