From 6d60bd8de25375eb5424adcd934310ceee74d74e Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 8 Aug 2007 02:27:10 +0000 Subject: [PATCH] - massive overhaul of the voting subsystem - NOTE: menu API compatibility was COMPLETELY broken for C++! - cleaned up various aspects of the menu API - implemented new extended vote results callback - extended the debug reporter a tiny bit --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401275 --- core/DebugReporter.cpp | 42 + core/DebugReporter.h | 1 + core/Makefile | 2 +- core/MenuManager.cpp | 279 +---- core/MenuManager.h | 44 +- core/MenuStyle_Base.cpp | 86 +- core/MenuStyle_Base.h | 13 +- core/MenuStyle_Radio.cpp | 20 +- core/MenuStyle_Radio.h | 3 +- core/MenuStyle_Valve.cpp | 26 +- core/MenuStyle_Valve.h | 3 +- core/MenuVoting.cpp | 326 +++++ core/MenuVoting.h | 97 ++ core/msvc8/sourcemod_mm.vcproj | 8 + core/smn_menus.cpp | 244 +++- core/vm/sp_vm_basecontext.cpp | 2056 ++++++++++++++++---------------- core/vm/sp_vm_engine.cpp | 10 + core/vm/sp_vm_engine.h | 1 - plugins/include/menus.inc | 48 +- public/IMenuManager.h | 191 ++- 20 files changed, 1900 insertions(+), 1600 deletions(-) create mode 100644 core/MenuVoting.cpp create mode 100644 core/MenuVoting.h diff --git a/core/DebugReporter.cpp b/core/DebugReporter.cpp index b1743493..f83ae24b 100644 --- a/core/DebugReporter.cpp +++ b/core/DebugReporter.cpp @@ -33,14 +33,56 @@ #include "DebugReporter.h" #include "Logger.h" #include "PluginSys.h" +#include "sm_stringutil.h" DebugReport g_DbgReporter; +/* I'm really lazy. This should probably be exported to ISourcePawnEngine someday, + * but we need to make sure the JIT will deal with the version bump. + */ +extern const char *GetSourcePawnErrorMessage(int error); + void DebugReport::OnSourceModAllInitialized() { g_pSourcePawn->SetDebugListener(this); } +void DebugReport::GenerateError(IPluginContext *ctx, cell_t func_idx, int err, const char *message, ...) +{ + va_list ap; + char buffer[512]; + + va_start(ap, message); + UTIL_FormatArgs(buffer, sizeof(buffer), message, ap); + va_end(ap); + + const char *plname = g_PluginSys.FindPluginByContext(ctx->GetContext())->GetFilename(); + const char *error = GetSourcePawnErrorMessage(err); + + if (error) + { + g_Logger.LogError("[SM] Plugin \"%s\" encountered error %d: %s", plname, err, error); + } else { + g_Logger.LogError("[SM] Plugin \"%s\" encountered unknown error %d", plname, err); + } + + g_Logger.LogError("[SM] %s", buffer); + + const char *func_name = NULL; + if (func_idx != -1) + { + if (func_idx & 1) + { + func_idx >>= 1; + sp_public_t *function; + if (ctx->GetPublicByIndex(func_idx, &function) == SP_ERROR_NONE) + { + g_Logger.LogError("[SM] Unable to call function \"%s\" due to above errors.", function->name); + } + } + } +} + void DebugReport::OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) { const char *lastname; diff --git a/core/DebugReporter.h b/core/DebugReporter.h index 3565b373..1cacff8a 100644 --- a/core/DebugReporter.h +++ b/core/DebugReporter.h @@ -44,6 +44,7 @@ public: // SMGlobalClass void OnSourceModAllInitialized(); public: // IDebugListener void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error); + void GenerateError(IPluginContext *ctx, cell_t func_idx, int err, const char *message, ...); private: int _GetPluginIndex(IPluginContext *ctx); }; diff --git a/core/Makefile b/core/Makefile index 11e61b84..26b0c31d 100644 --- a/core/Makefile +++ b/core/Makefile @@ -24,7 +24,7 @@ OBJECTS = AdminCache.cpp CDataPack.cpp ConCmdManager.cpp ConVarManager.cpp CoreC MemoryUtils.cpp PlayerManager.cpp TextParsers.cpp TimerSys.cpp Translator.cpp UserMessages.cpp \ sm_autonatives.cpp sm_memtable.cpp sm_srvcmds.cpp sm_stringutil.cpp sm_trie.cpp \ sourcemm_api.cpp sourcemod.cpp MenuStyle_Base.cpp MenuStyle_Valve.cpp MenuManager.cpp \ - MenuStyle_Radio.cpp ChatTriggers.cpp ADTFactory.cpp + MenuStyle_Radio.cpp ChatTriggers.cpp ADTFactory.cpp MenuVoting.cpp OBJECTS += smn_admin.cpp smn_bitbuffer.cpp smn_console.cpp smn_core.cpp \ smn_datapacks.cpp smn_entities.cpp smn_events.cpp smn_fakenatives.cpp \ smn_filesystem.cpp smn_float.cpp smn_functions.cpp smn_gameconfigs.cpp smn_halflife.cpp smn_handles.cpp smn_keyvalues.cpp \ diff --git a/core/MenuManager.cpp b/core/MenuManager.cpp index 2de5f73f..23733e95 100644 --- a/core/MenuManager.cpp +++ b/core/MenuManager.cpp @@ -34,6 +34,7 @@ #include #include #include "MenuManager.h" +#include "MenuVoting.h" #include "sm_stringutil.h" #include "sourcemm_api.h" #include "PlayerManager.h" @@ -43,220 +44,10 @@ #include "sourcemm_api.h" MenuManager g_Menus; +VoteMenuHandler s_VoteHandler; ConVar sm_menu_sounds("sm_menu_sounds", "1", 0, "Sets whether SourceMod menus play trigger sounds"); -/******************************* - ******************************* - ******** VOTE HANDLER ********* - ******************************* - *******************************/ - -unsigned int VoteMenuHandler::GetMenuAPIVersion2() -{ - return m_pHandler->GetMenuAPIVersion2(); -} - -bool VoteMenuHandler::IsVoteInProgress() -{ - return (m_pCurMenu != NULL); -} - -void VoteMenuHandler::InitializeVoting(IBaseMenu *menu) -{ - m_Items = menu->GetItemCount(); - - if (m_Votes.size() < (size_t)m_Items) - { - /* Only clear the items we need to... */ - size_t size = m_Votes.size(); - for (size_t i=0; iOnMenuStart(m_pCurMenu); -} - -void VoteMenuHandler::StartVoting() -{ - m_bStarted = true; - - m_pHandler->OnMenuVoteStart(m_pCurMenu); - - /* By now we know how many clients were set. - * If there are none, we should end IMMEDIATELY. - */ - if (m_Clients == 0) - { - EndVoting(); - } -} - -void VoteMenuHandler::DecrementPlayerCount() -{ - assert(m_Clients > 0); - - m_Clients--; - - if (m_bStarted && m_Clients == 0) - { - EndVoting(); - } -} - -void VoteMenuHandler::EndVoting() -{ - if (m_bCancelled) - { - /* If we were cancelled, don't bother tabulating anything. - * Reset just in case someone tries to redraw, which means - * we need to save our states. - */ - IBaseMenu *menu = m_pCurMenu; - InternalReset(); - m_pHandler->OnMenuVoteCancel(menu); - m_pHandler->OnMenuEnd(menu, MenuEnd_VotingCancelled); - return; - } - - unsigned int chosen = 0; - unsigned int highest = 0; - unsigned int dup_count = 0; - unsigned int total = m_Votes.size() ? m_Votes[0] : 0; - - /* If we got zero votes, take a shortcut. */ - if (m_NumVotes == 0) - { - /* Pick a random item and then jump far, far away. */ - srand((unsigned int)(time(NULL))); - chosen = (unsigned int)rand() % m_Items; - goto picked_item; - } - - /* We can't have more dups than this! - * This is the max number of players. - */ - unsigned int dup_array[256]; - - for (size_t i=1; i m_Votes[highest]) - { - /* If we have a new highest count, mark it and trash the duplicate - * list by setting the total to 0. - */ - highest = i; - dup_count = 0; - } else if (m_Votes[i] == m_Votes[highest]) { - /* If they're equal, mark it in the duplicate list. - * We'll add in the original later. - */ - dup_array[dup_count++] = i; - } - total += m_Votes[i]; - } - - /* Check if we need to pick from the duplicate list */ - if (dup_count) - { - /* Re-add the original to the list because it's not in there. */ - dup_array[dup_count++] = highest; - - /* Pick a random slot. */ - srand((unsigned int)(time(NULL))); - unsigned int r = (unsigned int)rand() % dup_count; - - /* Pick the item. */ - chosen = dup_array[r]; - } else { - chosen = highest; - } - -picked_item: - m_pHandler->OnMenuVoteEnd(m_pCurMenu, chosen, m_Votes[highest], total); - m_pHandler->OnMenuEnd(m_pCurMenu, MenuEnd_VotingDone); - InternalReset(); -} - -void VoteMenuHandler::OnMenuStart(IBaseMenu *menu) -{ - m_Clients++; -} - -void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) -{ - DecrementPlayerCount(); -} - -void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) -{ - m_pHandler->OnMenuCancel(menu, client, reason); -} - -void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display) -{ - m_pHandler->OnMenuDisplay(menu, client, display); -} - -unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr) -{ - return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr); -} - -void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) -{ - m_pHandler->OnMenuDrawItem(menu, client, item, style); -} - -void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) -{ - /* Check by our item count, NOT the vote array size */ - if (item < m_Items) - { - m_Votes[item]++; - m_NumVotes++; - } - - m_pHandler->OnMenuSelect(menu, client, item); -} - -void VoteMenuHandler::Reset(IMenuHandler *mh) -{ - m_pHandler = mh; - InternalReset(); -} - -void VoteMenuHandler::InternalReset() -{ - m_Clients = 0; - m_Items = 0; - m_bStarted = false; - m_pCurMenu = NULL; - m_NumVotes = 0; - m_bCancelled = false; -} - -void VoteMenuHandler::CancelVoting() -{ - m_bCancelled = true; -} - -/******************************* - ******************************* - ******** MENU MANAGER ********* - ******************************* - *******************************/ - MenuManager::MenuManager() { m_Styles.push_back(&g_ValveMenuStyle); @@ -283,12 +74,6 @@ void MenuManager::OnSourceModAllShutdown() { g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent); g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent); - - while (!m_VoteHandlers.empty()) - { - delete m_VoteHandlers.front(); - m_VoteHandlers.pop(); - } } void MenuManager::OnHandleDestroy(HandleType_t type, void *object) @@ -649,8 +434,8 @@ skip_search: if (pgn != MENU_NO_PAGINATION) { bool canDrawDisabled = display->CanDrawItem(ITEMDRAW_DISABLED|ITEMDRAW_CONTROL); - bool exitButton = menu->GetExitButton(); - bool exitBackButton = menu->GetExitBackButton(); + bool exitButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT; + bool exitBackButton = (menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXITBACK; char text[50]; /* Calculate how many items we are allowed for control stuff */ @@ -798,33 +583,6 @@ IMenuStyle *MenuManager::GetDefaultStyle() return m_pDefaultStyle; } -IVoteMenuHandler *MenuManager::CreateVoteWrapper(IMenuHandler *mh) -{ - VoteMenuHandler *vh = NULL; - - if (m_VoteHandlers.empty()) - { - vh = new VoteMenuHandler; - } else { - vh = m_VoteHandlers.front(); - m_VoteHandlers.pop(); - } - - vh->Reset(mh); - - return vh; -} - -void MenuManager::ReleaseVoteWrapper(IVoteMenuHandler *mh) -{ - if (mh == NULL) - { - return; - } - - m_VoteHandlers.push((VoteMenuHandler *)mh); -} - bool MenuManager::MenuSoundsEnabled() { return (sm_menu_sounds.GetInt() != 0); @@ -879,7 +637,7 @@ const char *MenuManager::GetMenuSound(ItemSelection sel) { if (m_ExitSound.size() > 0) { - sound= m_ExitSound.c_str(); + sound = m_ExitSound.c_str(); } break; } @@ -907,3 +665,30 @@ void MenuManager::OnSourceModLevelChange(const char *mapName) enginesound->PrecacheSound(m_ExitSound.c_str(), true); } } + +void MenuManager::CancelMenu(IBaseMenu *menu) +{ + if (s_VoteHandler.GetCurrentMenu() == menu + && !s_VoteHandler.IsCancelling()) + { + s_VoteHandler.CancelVoting(); + return; + } + + menu->Cancel(); +} + +bool MenuManager::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags) +{ + return s_VoteHandler.StartVote(menu, num_clients, clients, max_time, flags); +} + +bool MenuManager::IsVoteInProgress() +{ + return s_VoteHandler.IsVoteInProgress(); +} + +void MenuManager::CancelVoting() +{ + s_VoteHandler.CancelVoting(); +} diff --git a/core/MenuManager.h b/core/MenuManager.h index 871080a4..86c87759 100644 --- a/core/MenuManager.h +++ b/core/MenuManager.h @@ -44,39 +44,6 @@ using namespace SourceMod; using namespace SourceHook; -class VoteMenuHandler : public IVoteMenuHandler -{ -public: //IMenuHandler - unsigned int GetMenuAPIVersion2(); - void OnMenuStart(IBaseMenu *menu); - void OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display); - void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); - void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); - void OnMenuEnd(IBaseMenu *menu, MenuEndReason 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); -public: //IVoteMenuHandler - bool IsVoteInProgress(); - void InitializeVoting(IBaseMenu *menu); - void StartVoting(); - void CancelVoting(); -public: - void Reset(IMenuHandler *mh); -private: - void DecrementPlayerCount(); - void EndVoting(); - void InternalReset(); -private: - IMenuHandler *m_pHandler; - unsigned int m_Clients; - unsigned int m_Items; - CVector m_Votes; - IBaseMenu *m_pCurMenu; - bool m_bStarted; - bool m_bCancelled; - unsigned int m_NumVotes; -}; - class MenuManager : public IMenuManager, public SMGlobalClass, @@ -114,8 +81,14 @@ public: void AddStyle(IMenuStyle *style); bool SetDefaultStyle(IMenuStyle *style); IMenuPanel *RenderMenu(int client, menu_states_t &states, ItemOrder order); - IVoteMenuHandler *CreateVoteWrapper(IMenuHandler *mh); - void ReleaseVoteWrapper(IVoteMenuHandler *mh); + void CancelMenu(IBaseMenu *menu); + bool StartVote(IBaseMenu *menu, + unsigned int num_clients, + int clients[], + unsigned int max_time, + unsigned int flags=0); + bool IsVoteInProgress(); + void CancelVoting(); public: //IHandleTypeDispatch void OnHandleDestroy(HandleType_t type, void *object); public: @@ -130,7 +103,6 @@ protected: private: int m_ShowMenu; IMenuStyle *m_pDefaultStyle; - CStack m_VoteHandlers; CVector m_Styles; HandleType_t m_StyleType; HandleType_t m_MenuType; diff --git a/core/MenuStyle_Base.cpp b/core/MenuStyle_Base.cpp index 1325f7a3..92a8e139 100644 --- a/core/MenuStyle_Base.cpp +++ b/core/MenuStyle_Base.cpp @@ -584,14 +584,12 @@ bool BaseMenuStyle::RedoClientMenu(int client, ItemOrder order) CBaseMenu::CBaseMenu(IMenuHandler *pHandler, IMenuStyle *pStyle, IdentityToken_t *pOwner) : m_pStyle(pStyle), m_Strings(512), m_Pagination(7), m_bShouldDelete(false), m_bCancelling(false), m_pOwner(pOwner ? pOwner : g_pCoreIdent), m_bDeleting(false), m_bWillFreeHandle(false), -m_hHandle(BAD_HANDLE), m_pHandler(pHandler), m_pVoteHandler(NULL), -m_nFlags(MENUFLAG_BUTTON_EXIT) +m_hHandle(BAD_HANDLE), m_pHandler(pHandler), m_nFlags(MENUFLAG_BUTTON_EXIT) { } CBaseMenu::~CBaseMenu() { - g_Menus.ReleaseVoteWrapper(m_pVoteHandler); } Handle_t CBaseMenu::GetHandle() @@ -732,22 +730,6 @@ const char *CBaseMenu::GetDefaultTitle() return m_Title.c_str(); } -bool CBaseMenu::GetExitButton() -{ - return ((m_nFlags & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT); -} - -bool CBaseMenu::SetExitButton(bool set) -{ - if (set) - { - m_nFlags |= MENUFLAG_BUTTON_EXIT; - } else { - m_nFlags &= ~MENUFLAG_BUTTON_EXIT; - } - return true; -} - void CBaseMenu::Cancel() { if (m_bCancelling) @@ -756,18 +738,11 @@ void CBaseMenu::Cancel() } #if defined MENU_DEBUG - g_Logger.LogMessage("[SM_MENU] CBaseMenu::Cancel(%p) (m_pVote %p) (inVote %d) (m_bShouldDelete %d)", + g_Logger.LogMessage("[SM_MENU] CBaseMenu::Cancel(%p) (m_bShouldDelete %d)", this, - m_pVoteHandler, - m_pVoteHandler ? m_pVoteHandler->IsVoteInProgress() : false, m_bShouldDelete); #endif - if (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress()) - { - m_pVoteHandler->CancelVoting(); - } - m_bCancelling = true; Cancel_Finally(); m_bCancelling = false; @@ -827,58 +802,6 @@ void CBaseMenu::InternalDelete() delete this; } -bool CBaseMenu::BroadcastVote(int clients[], - unsigned int numClients, - unsigned int maxTime, - unsigned int flags) -{ -#if defined MENU_DEBUG - g_Logger.LogMessage("[SM_MENU] CBaseMenu::BroadcastVote(%p) (maxTime %d) (numClients %d) (m_pVote %p) (inVote %d)", - this, - maxTime, - numClients, - m_pVoteHandler, - m_pVoteHandler ? m_pVoteHandler->IsVoteInProgress() : false); -#endif - if (!m_pVoteHandler) - { - m_pVoteHandler = g_Menus.CreateVoteWrapper(m_pHandler); - } else if (m_pVoteHandler->IsVoteInProgress()) { - return false; - } - - m_pVoteHandler->InitializeVoting(this); - - for (unsigned int i=0; iStartVoting(); - - return true; -} - -bool CBaseMenu::IsVoteInProgress() -{ - return (m_pVoteHandler && m_pVoteHandler->IsVoteInProgress()); -} - -bool CBaseMenu::GetExitBackButton() -{ - return ((m_nFlags & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXITBACK); -} - -void CBaseMenu::SetExitBackButton(bool set) -{ - if (set) - { - m_nFlags |= MENUFLAG_BUTTON_EXITBACK; - } else { - m_nFlags &= ~MENUFLAG_BUTTON_EXITBACK; - } -} - unsigned int CBaseMenu::GetMenuOptionFlags() { return m_nFlags; @@ -888,3 +811,8 @@ void CBaseMenu::SetMenuOptionFlags(unsigned int flags) { m_nFlags = flags; } + +IMenuHandler *CBaseMenu::GetHandler() +{ + return m_pHandler; +} diff --git a/core/MenuStyle_Base.h b/core/MenuStyle_Base.h index 3e4f6a7c..6cf8da1f 100644 --- a/core/MenuStyle_Base.h +++ b/core/MenuStyle_Base.h @@ -125,23 +125,13 @@ public: virtual IMenuStyle *GetDrawStyle(); virtual void SetDefaultTitle(const char *message); virtual const char *GetDefaultTitle(); - virtual bool GetExitButton(); - virtual bool SetExitButton(bool set); virtual void Cancel(); virtual void Destroy(bool releaseHandle); virtual void Cancel_Finally() =0; virtual Handle_t GetHandle(); - virtual bool BroadcastVote(int clients[], - unsigned int numClients, - unsigned int maxTime, - unsigned int flags=0); - virtual bool IsVoteInProgress(); - virtual bool GetExitBackButton(); - virtual void SetExitBackButton(bool set); virtual unsigned int GetMenuOptionFlags(); virtual void SetMenuOptionFlags(unsigned int flags); -public: - virtual void VoteDisplay(int client, unsigned int maxTime) =0; + virtual IMenuHandler *GetHandler(); private: void InternalDelete(); protected: @@ -157,7 +147,6 @@ protected: bool m_bWillFreeHandle; Handle_t m_hHandle; IMenuHandler *m_pHandler; - IVoteMenuHandler *m_pVoteHandler; unsigned int m_nFlags; }; diff --git a/core/MenuStyle_Radio.cpp b/core/MenuStyle_Radio.cpp index ebb404b5..0d67fdf7 100644 --- a/core/MenuStyle_Radio.cpp +++ b/core/MenuStyle_Radio.cpp @@ -390,7 +390,7 @@ IMenuPanel *CRadioMenu::CreatePanel() return g_RadioMenuStyle.MakeRadioDisplay(this); } -bool CRadioMenu::Display(int client, unsigned int time) +bool CRadioMenu::Display(int client, unsigned int time, IMenuHandler *alt_handler) { #if defined MENU_DEBUG g_Logger.LogMessage("[SM_MENU] CRadioMenu::Display(%p) (client %d) (time %d)", @@ -403,23 +403,7 @@ bool CRadioMenu::Display(int client, unsigned int time) return false; } - return g_RadioMenuStyle.DoClientMenu(client, this, m_pHandler, time); -} - -void CRadioMenu::VoteDisplay(int client, unsigned int maxTime) -{ -#if defined MENU_DEBUG - g_Logger.LogMessage("[SM_MENU] CRadioMenu::VoteDisplay(%p) (client %d) (time %d)", - this, - client, - maxTime); -#endif - if (m_bCancelling) - { - return; - } - - g_RadioMenuStyle.DoClientMenu(client, this, m_pVoteHandler, maxTime); + return g_RadioMenuStyle.DoClientMenu(client, this, alt_handler ? alt_handler : m_pHandler, time); } void CRadioMenu::Cancel_Finally() diff --git a/core/MenuStyle_Radio.h b/core/MenuStyle_Radio.h index a160a0b8..a7bf0263 100644 --- a/core/MenuStyle_Radio.h +++ b/core/MenuStyle_Radio.h @@ -114,9 +114,8 @@ public: public: bool SetExtOption(MenuOption option, const void *valuePtr); IMenuPanel *CreatePanel(); - bool Display(int client, unsigned int time); + bool Display(int client, unsigned int time, IMenuHandler *alt_handler=NULL); void Cancel_Finally(); - void VoteDisplay(int client, unsigned int maxTime); }; extern CRadioStyle g_RadioMenuStyle; diff --git a/core/MenuStyle_Valve.cpp b/core/MenuStyle_Valve.cpp index 5e4fc7a7..99936652 100644 --- a/core/MenuStyle_Valve.cpp +++ b/core/MenuStyle_Valve.cpp @@ -383,24 +383,14 @@ bool CValveMenu::SetExtOption(MenuOption option, const void *valuePtr) return false; } -bool CValveMenu::Display(int client, unsigned int time) +bool CValveMenu::Display(int client, unsigned int time, IMenuHandler *alt_handler) { if (m_bCancelling) { return false; } - return g_ValveMenuStyle.DoClientMenu(client, this, m_pHandler, time); -} - -void CValveMenu::VoteDisplay(int client, unsigned int maxTime) -{ - if (m_bCancelling) - { - return; - } - - g_ValveMenuStyle.DoClientMenu(client, this, m_pVoteHandler, maxTime); + return g_ValveMenuStyle.DoClientMenu(client, this, alt_handler ? alt_handler : m_pHandler, time); } IMenuPanel *CValveMenu::CreatePanel() @@ -408,19 +398,9 @@ IMenuPanel *CValveMenu::CreatePanel() return new CValveMenuDisplay(this); } -bool CValveMenu::GetExitButton() -{ - return true; -} - -bool CValveMenu::SetExitButton(bool set) -{ - return false; -} - void CValveMenu::SetMenuOptionFlags(unsigned int flags) { - flags &= ~MENUFLAG_BUTTON_EXIT; + flags |= MENUFLAG_BUTTON_EXIT; CBaseMenu::SetMenuOptionFlags(flags); } diff --git a/core/MenuStyle_Valve.h b/core/MenuStyle_Valve.h index c4c7eb9f..c0c28a5e 100644 --- a/core/MenuStyle_Valve.h +++ b/core/MenuStyle_Valve.h @@ -120,8 +120,7 @@ public: //IBaseMenu bool GetExitButton(); bool SetExitButton(bool set); bool SetPagination(unsigned int itemsPerPage); - bool Display(int client, unsigned int time); - void VoteDisplay(int client, unsigned int maxTime); + bool Display(int client, unsigned int time, IMenuHandler *alt_handler=NULL); void SetMenuOptionFlags(unsigned int flags); public: //CBaseMenu void Cancel_Finally(); diff --git a/core/MenuVoting.cpp b/core/MenuVoting.cpp new file mode 100644 index 00000000..1d1b34ca --- /dev/null +++ b/core/MenuVoting.cpp @@ -0,0 +1,326 @@ +/** + * vim: set ts=4 : + * ================================================================ + * SourceMod + * Copyright (C) 2004-2007 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 +#include +#include "MenuVoting.h" +#include "PlayerManager.h" +#include "sourcemm_api.h" + +void VoteMenuHandler::OnSourceModAllInitialized() +{ + g_Players.AddClientListener(this); +} + +void VoteMenuHandler::OnSourceModShutdown() +{ + g_Players.RemoveClientListener(this); +} + +unsigned int VoteMenuHandler::GetMenuAPIVersion2() +{ + return m_pHandler->GetMenuAPIVersion2(); +} + +void VoteMenuHandler::OnClientDisconnected(int client) +{ + if (!IsVoteInProgress()) + { + return; + } + + /* Wipe out their vote if they had one */ + int item; + if ((item = m_ClientVotes[client]) >= 0) + { + assert((unsigned)item < m_Items); + assert(m_Votes[item] > 0); + m_Votes[item]--; + m_ClientVotes[client] = -1; + } +} + +bool VoteMenuHandler::IsVoteInProgress() +{ + return (m_pCurMenu != NULL); +} + +bool VoteMenuHandler::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags/* =0 */) +{ + if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags)) + { + return false; + } + + for (unsigned int i=0; iDisplay(clients[i], max_time, this); + } + + StartVoting(); + + return true; +} + +bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, + IMenuHandler *handler, + unsigned int time, + unsigned int flags) +{ + if (IsVoteInProgress()) + { + return false; + } + + InternalReset(); + + /* Mark all clients as not voting */ + for (int i=1; i<=gpGlobals->maxClients; i++) + { + m_ClientVotes[i] = -2; + } + + m_Items = menu->GetItemCount(); + + if (m_Votes.size() < (size_t)m_Items) + { + /* Only clear the items we need to... */ + size_t size = m_Votes.size(); + for (size_t i=0; iOnMenuStart(m_pCurMenu); + + return true; +} + +void VoteMenuHandler::StartVoting() +{ + if (!m_pCurMenu) + { + return; + } + + m_bStarted = true; + + m_pHandler->OnMenuVoteStart(m_pCurMenu); + + /* By now we know how many clients were set. + * If there are none, we should end IMMEDIATELY. + */ + if (m_Clients == 0) + { + EndVoting(); + } +} + +void VoteMenuHandler::DecrementPlayerCount() +{ + assert(m_Clients > 0); + + m_Clients--; + + if (m_bStarted && m_Clients == 0) + { + EndVoting(); + } +} + +int SortVoteItems(const void *item1, const void *item2) +{ + return ((menu_vote_result_t::menu_item_vote_t *)item2)->count + - ((menu_vote_result_t::menu_item_vote_t *)item1)->count; +} + +void VoteMenuHandler::EndVoting() +{ + if (m_bCancelled) + { + /* If we were cancelled, don't bother tabulating anything. + * Reset just in case someone tries to redraw, which means + * we need to save our states. + */ + IBaseMenu *menu = m_pCurMenu; + IMenuHandler *handler = m_pHandler; + InternalReset(); + handler->OnMenuVoteCancel(menu); + handler->OnMenuEnd(menu, MenuEnd_VotingCancelled); + return; + } + + menu_vote_result_t vote; + menu_vote_result_t::menu_client_vote_t client_vote[256]; + menu_vote_result_t::menu_item_vote_t item_vote[256]; + + memset(&vote, 0, sizeof(vote)); + + /* Build the item list */ + for (unsigned int i=0; i 0) + { + item_vote[vote.num_items].count = m_Votes[i]; + item_vote[vote.num_items].item = i; + vote.num_votes += m_Votes[i]; + vote.num_items++; + } + } + vote.item_list = item_vote; + + if (!vote.num_votes) + { + IBaseMenu *menu = m_pCurMenu; + IMenuHandler *handler = m_pHandler; + InternalReset(); + handler->OnMenuVoteCancel(menu); + handler->OnMenuEnd(menu, MenuEnd_NoVotes); + return; + } + + /* Build the client list */ + for (int i=1; i<=gpGlobals->maxClients; i++) + { + if (m_ClientVotes[i] >= -1) + { + client_vote[vote.num_clients].client = i; + client_vote[vote.num_clients].item = m_ClientVotes[i]; + vote.num_clients++; + } + } + vote.client_list = client_vote; + + /* Sort the item list descending like we promised */ + qsort(item_vote, + vote.num_items, + sizeof(menu_vote_result_t::menu_item_vote_t), + SortVoteItems); + + /* Save states, then clear what we've saved. + * This makes us re-entrant, which is always the safe way to go. + */ + IBaseMenu *menu = m_pCurMenu; + IMenuHandler *handler = m_pHandler; + InternalReset(); + + /* Send vote info */ + handler->OnMenuVoteResults(menu, &vote); + handler->OnMenuEnd(menu, MenuEnd_VotingDone); +} + +void VoteMenuHandler::OnMenuStart(IBaseMenu *menu) +{ + m_Clients++; +} + +void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) +{ + DecrementPlayerCount(); +} + +void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) +{ + m_pHandler->OnMenuCancel(menu, client, reason); +} + +void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display) +{ + m_ClientVotes[client] = -1; + m_pHandler->OnMenuDisplay(menu, client, display); +} + +unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr) +{ + return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr); +} + +void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) +{ + m_pHandler->OnMenuDrawItem(menu, client, item, style); +} + +void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) +{ + /* Check by our item count, NOT the vote array size */ + if (item < m_Items) + { + m_ClientVotes[client] = item; + m_Votes[item]++; + m_NumVotes++; + } + + m_pHandler->OnMenuSelect(menu, client, item); +} + +void VoteMenuHandler::InternalReset() +{ + m_Clients = 0; + m_Items = 0; + m_bStarted = false; + m_pCurMenu = NULL; + m_NumVotes = 0; + m_bCancelled = false; + m_pHandler = NULL; +} + +void VoteMenuHandler::CancelVoting() +{ + if (m_bCancelled || !m_pCurMenu) + { + return; + } + m_bCancelled = true; + m_pCurMenu->Cancel(); +} + +IBaseMenu *VoteMenuHandler::GetCurrentMenu() +{ + return m_pCurMenu; +} + +bool VoteMenuHandler::IsCancelling() +{ + return m_bCancelled; +} diff --git a/core/MenuVoting.h b/core/MenuVoting.h new file mode 100644 index 00000000..6b0a1a8d --- /dev/null +++ b/core/MenuVoting.h @@ -0,0 +1,97 @@ +/** + * vim: set ts=4 : + * ================================================================ + * SourceMod + * Copyright (C) 2004-2007 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$ + */ + +#ifndef _INCLUDE_SOURCEMOD_MENUVOTING_H_ +#define _INCLUDE_SOURCEMOD_MENUVOTING_H_ + +#include +#include +#include +#include "sm_globals.h" + +using namespace SourceHook; +using namespace SourceMod; + +class VoteMenuHandler : + public IMenuHandler, + public SMGlobalClass, + public IClientListener +{ +public: //SMGlobalClass + void OnSourceModAllInitialized(); + void OnSourceModShutdown(); +public: //IClientListener + void OnClientDisconnected(int client); +public: //IMenuHandler + unsigned int GetMenuAPIVersion2(); + void OnMenuStart(IBaseMenu *menu); + void OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display); + void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item); + void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason); + void OnMenuEnd(IBaseMenu *menu, MenuEndReason 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); +public: + bool StartVote(IBaseMenu *menu, + unsigned int num_clients, + int clients[], + unsigned int max_time, + unsigned int flags=0); + bool IsVoteInProgress(); + void CancelVoting(); + IBaseMenu *GetCurrentMenu(); + bool IsCancelling(); +private: + void Reset(IMenuHandler *mh); + void DecrementPlayerCount(); + void EndVoting(); + void InternalReset(); + bool InitializeVoting(IBaseMenu *menu, + IMenuHandler *handler, + unsigned int time, + unsigned int flags); + void StartVoting(); +private: + IMenuHandler *m_pHandler; + unsigned int m_Clients; + unsigned int m_Items; + CVector m_Votes; + IBaseMenu *m_pCurMenu; + bool m_bStarted; + bool m_bCancelled; + unsigned int m_NumVotes; + unsigned int m_VoteTime; + unsigned int m_VoteFlags; + int m_ClientVotes[256+1]; +}; + +#endif //_INCLUDE_SOURCEMOD_MENUVOTING_H_ diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index 07cd319b..96dc555a 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -336,6 +336,10 @@ RelativePath="..\MenuStyle_Valve.cpp" > + + @@ -474,6 +478,10 @@ RelativePath="..\MenuStyle_Valve.h" > + + diff --git a/core/smn_menus.cpp b/core/smn_menus.cpp index 233cb7de..4e54d971 100644 --- a/core/smn_menus.cpp +++ b/core/smn_menus.cpp @@ -32,6 +32,7 @@ #include "sm_globals.h" #include +#include "DebugReporter.h" #include "MenuManager.h" #include "MenuStyle_Valve.h" #include "MenuStyle_Radio.h" @@ -93,13 +94,11 @@ public: void OnMenuEnd(IBaseMenu *menu, MenuEndReason reason); void OnMenuDestroy(IBaseMenu *menu); void OnMenuVoteStart(IBaseMenu *menu); - void OnMenuVoteEnd(IBaseMenu *menu, - unsigned int item, - unsigned int winningVotes, - unsigned int totalVotes); + void OnMenuVoteResults(IBaseMenu *menu, const menu_vote_result_t *results); void OnMenuVoteCancel(IBaseMenu *menu); 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); @@ -109,6 +108,8 @@ private: private: IPluginFunction *m_pBasic; int m_Flags; + IPluginFunction *m_pVoteResults; + cell_t m_fnVoteResult; }; /** @@ -158,7 +159,7 @@ public: } /** - * It is extremely important that unloaded plugins don't crash. + * 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 @@ -221,6 +222,7 @@ public: m_FreeMenuHandlers.pop(); handler->m_pBasic = pFunction; handler->m_Flags = flags; + handler->m_pVoteResults = NULL; } return handler; } @@ -276,7 +278,7 @@ static const ItemDrawInfo *s_CurDrawInfo = NULL; * MENU HANDLER WRAPPER */ CMenuHandler::CMenuHandler(IPluginFunction *pBasic, int flags) : - m_pBasic(pBasic), m_Flags(flags) + m_pBasic(pBasic), m_Flags(flags), m_pVoteResults(NULL) { /* :TODO: We can probably cache the handle ahead of time */ } @@ -334,14 +336,6 @@ void CMenuHandler::OnMenuVoteStart(IBaseMenu *menu) DoAction(menu, MenuAction_VoteStart, 0, 0); } -void CMenuHandler::OnMenuVoteEnd(IBaseMenu *menu, - unsigned int item, - unsigned int winningVotes, - unsigned int totalVotes) -{ - DoAction(menu, MenuAction_VoteEnd, item, (totalVotes << 16) | (winningVotes & 0xFFFF)); -} - void CMenuHandler::OnMenuVoteCancel(IBaseMenu *menu) { DoAction(menu, MenuAction_VoteCancel, 0, 0); @@ -407,6 +401,145 @@ cell_t CMenuHandler::DoAction(IBaseMenu *menu, MenuAction action, cell_t param1, 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) + { + g_DbgReporter.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) + { + g_DbgReporter.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(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 */ @@ -488,6 +621,11 @@ static cell_t DisplayMenu(IPluginContext *pContext, const cell_t *params) 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; @@ -500,7 +638,12 @@ static cell_t VoteMenu(IPluginContext *pContext, const cell_t *params) cell_t *addr; pContext->LocalToPhysAddr(params[2], &addr); - return menu->BroadcastVote(addr, params[3], params[4]) ? 1 : 0; + if (!g_Menus.StartVote(menu, params[3], addr, params[4])) + { + return 0; + } + + return 1; } static cell_t AddMenuItem(IPluginContext *pContext, const cell_t *params) @@ -699,7 +842,7 @@ static cell_t GetMenuExitButton(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); } - return menu->GetExitButton() ? 1 : 0; + return ((menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT) ? 1 : 0; } static cell_t GetMenuExitBackButton(IPluginContext *pContext, const cell_t *params) @@ -713,7 +856,7 @@ static cell_t GetMenuExitBackButton(IPluginContext *pContext, const cell_t *para return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); } - return menu->GetExitBackButton() ? 1 : 0; + return ((menu->GetMenuOptionFlags() & MENUFLAG_BUTTON_EXITBACK) == MENUFLAG_BUTTON_EXIT) ? 1 : 0; } static cell_t SetMenuExitButton(IPluginContext *pContext, const cell_t *params) @@ -727,7 +870,11 @@ static cell_t SetMenuExitButton(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); } - return menu->SetExitButton(params[2] ? true : false) ? 1 : 0; + unsigned int flags = menu->GetMenuOptionFlags(); + flags |= MENUFLAG_BUTTON_EXIT; + menu->SetMenuOptionFlags(flags); + flags = menu->GetMenuOptionFlags(); + return ((flags & MENUFLAG_BUTTON_EXIT) == MENUFLAG_BUTTON_EXIT) ? 1 : 0; } static cell_t SetMenuExitBackButton(IPluginContext *pContext, const cell_t *params) @@ -741,7 +888,9 @@ static cell_t SetMenuExitBackButton(IPluginContext *pContext, const cell_t *para return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); } - menu->SetExitBackButton(params[2] ? true : false); + unsigned int flags = menu->GetMenuOptionFlags(); + flags |= MENUFLAG_BUTTON_EXITBACK; + menu->SetMenuOptionFlags(flags); return 1; } @@ -757,23 +906,14 @@ static cell_t CancelMenu(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); } - menu->Cancel(); + g_Menus.CancelMenu(menu); return 1; } static cell_t IsVoteInProgress(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->IsVoteInProgress() ? 1 : 0; + return g_Menus.IsVoteInProgress() ? 1 : 0; } static cell_t GetMenuStyle(IPluginContext *pContext, const cell_t *params) @@ -1122,12 +1262,55 @@ static cell_t SetMenuOptionFlags(IPluginContext *pContext, const cell_t *params) 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; +} + REGISTER_NATIVES(menuNatives) { {"AddMenuItem", AddMenuItem}, {"CanPanelDrawFlags", CanPanelDrawFlags}, {"CancelClientMenu", CancelClientMenu}, {"CancelMenu", CancelMenu}, + {"CancelVote", CancelVote}, {"CreateMenu", CreateMenu}, {"CreateMenuEx", CreateMenuEx}, {"CreatePanel", CreatePanel}, @@ -1161,6 +1344,7 @@ REGISTER_NATIVES(menuNatives) {"SetPanelCurrentKey", SetPanelCurrentKey}, {"SetPanelTitle", SetPanelTitle}, {"SetPanelKeys", SetPanelKeys}, + {"SetVoteResultCallback", SetVoteResultCallback}, {"VoteMenu", VoteMenu}, {NULL, NULL}, }; diff --git a/core/vm/sp_vm_basecontext.cpp b/core/vm/sp_vm_basecontext.cpp index 8eb52653..36b025a3 100644 --- a/core/vm/sp_vm_basecontext.cpp +++ b/core/vm/sp_vm_basecontext.cpp @@ -28,1031 +28,1031 @@ * JULY-31-2007), or . * * Version: $Id$ - */ - -#include -#include -#include -#include -#include "sp_vm_api.h" -#include "sp_vm_basecontext.h" -#include "sp_vm_engine.h" - -#ifdef SOURCEMOD_BUILD -#include "Logger.h" -#endif - -using namespace SourcePawn; - -extern SourcePawnEngine g_SourcePawn; - -#define CELLBOUNDMAX (INT_MAX/sizeof(cell_t)) -#define STACKMARGIN ((cell_t)(16*sizeof(cell_t))) - -int GlobalDebugBreak(sp_context_t *ctx, uint32_t frm, uint32_t cip) -{ - g_SourcePawn.RunTracer(ctx, frm, cip); - - return SP_ERROR_NONE; -} - -BaseContext::BaseContext(sp_context_t *_ctx) -{ - ctx = _ctx; - ctx->context = this; - ctx->dbreak = GlobalDebugBreak; - m_InExec = false; - m_CustomMsg = false; - m_funcsnum = ctx->vmbase->FunctionCount(ctx); -#if 0 - m_priv_funcs = NULL; -#endif - m_pub_funcs = NULL; - -#if 0 - /** - * Note: Since the m_plugin member will never change, - * it is safe to assume the function count will never change - */ - if (m_funcsnum && m_priv_funcs == NULL) - { - m_priv_funcs = new CFunction *[m_funcsnum]; - memset(m_priv_funcs, 0, sizeof(CFunction *) * m_funcsnum); - } else { - m_priv_funcs = NULL; - } -#endif - - if (ctx->plugin->info.publics_num && m_pub_funcs == NULL) - { - m_pub_funcs = new CFunction *[ctx->plugin->info.publics_num]; - memset(m_pub_funcs, 0, sizeof(CFunction *) * ctx->plugin->info.publics_num); - } else { - m_pub_funcs = NULL; - } - - /* Initialize the null references */ - uint32_t index; - if (FindPubvarByName("NULL_VECTOR", &index) == SP_ERROR_NONE) - { - sp_pubvar_t *pubvar; - GetPubvarByIndex(index, &pubvar); - m_pNullVec = pubvar->offs; - } else { - m_pNullVec = NULL; - } - - if (FindPubvarByName("NULL_STRING", &index) == SP_ERROR_NONE) - { - sp_pubvar_t *pubvar; - GetPubvarByIndex(index, &pubvar); - m_pNullString = pubvar->offs; - } else { - m_pNullString = NULL; - } -} - -void BaseContext::FlushFunctionCache() -{ - if (m_pub_funcs) - { - for (uint32_t i=0; iplugin->info.publics_num; i++) - { - delete m_pub_funcs[i]; - m_pub_funcs[i] = NULL; - } - } - -#if 0 - if (m_priv_funcs) - { - for (unsigned int i=0; iplugin->info.publics_num; i++) - { - if (!m_pub_funcs[i]) - { - continue; - } - if (GetPublicByIndex(i, &pub) != SP_ERROR_NONE) - { - continue; - } - m_pub_funcs[i]->Set(pub->code_offs, this); - } - } - -#if 0 - if (m_priv_funcs) - { - for (unsigned int i=0; i - } - } -#endif -} - -BaseContext::~BaseContext() -{ - FlushFunctionCache(); - delete [] m_pub_funcs; - m_pub_funcs = NULL; -#if 0 - delete [] m_priv_funcs; - m_priv_funcs = NULL; -#endif -} - -void BaseContext::SetContext(sp_context_t *_ctx) -{ - if (!_ctx) - { - return; - } - ctx = _ctx; - ctx->context = this; - ctx->dbreak = GlobalDebugBreak; - RefreshFunctionCache(); -} - -IVirtualMachine *BaseContext::GetVirtualMachine() -{ - return (IVirtualMachine *)ctx->vmbase; -} - -sp_context_t *BaseContext::GetContext() -{ - return ctx; -} - -bool BaseContext::IsDebugging() -{ - return (ctx->flags & SPFLAG_PLUGIN_DEBUG); -} - -int BaseContext::SetDebugBreak(SPVM_DEBUGBREAK newpfn, SPVM_DEBUGBREAK *oldpfn) -{ - if (!IsDebugging()) - { - return SP_ERROR_NOTDEBUGGING; - } - - *oldpfn = ctx->dbreak; - ctx->dbreak = newpfn; - - return SP_ERROR_NONE; -} - -IPluginDebugInfo *BaseContext::GetDebugInfo() -{ - return this; -} - -int BaseContext::Execute(uint32_t code_addr, cell_t *result) -{ - if ((ctx->flags & SPFLAG_PLUGIN_PAUSED) == SPFLAG_PLUGIN_PAUSED) - { - 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; - int err; - - PushCell(pushcount++); - ctx->pushcount = 0; - - cell_t save_sp = ctx->sp; - cell_t save_hp = ctx->hp; - uint32_t n_idx = ctx->n_idx; - - bool wasExec = m_InExec; - - /* Clear the error state, if any */ - ctx->n_err = SP_ERROR_NONE; - ctx->n_idx = 0; - m_InExec = true; - m_MsgCache[0] = '\0'; - m_CustomMsg = false; - - g_SourcePawn.PushTracer(ctx); - - err = vm->ContextExecute(ctx, code_addr, result); - - m_InExec = wasExec; - - /** - * :TODO: Calling from a plugin in here will erase the cached message... - * Should that be documented? - */ - g_SourcePawn.PopTracer(err, m_CustomMsg ? m_MsgCache : NULL); - -#if 1//defined _DEBUG - //:TODO: debug code for leak detection, remove before the release? - if (err == SP_ERROR_NONE) - { - if ((ctx->sp - (cell_t)(pushcount * sizeof(cell_t))) != save_sp) - { - const char *name; - ctx->context->GetDebugInfo()->LookupFunction(code_addr, &name); - g_Logger.LogError("Stack leak detected: sp:%d should be %d on function %s", ctx->sp, save_sp, name); - } - if (ctx->hp != save_hp) - { - const char *name; - ctx->context->GetDebugInfo()->LookupFunction(code_addr, &name); - g_Logger.LogError("Heap leak detected: hp:%d should be %d on function %s", ctx->hp, save_hp, name); - } - //assert(ctx->sp - pushcount * sizeof(cell_t) == save_sp); - //assert(ctx->hp == save_hp); - } -#endif - if (err != SP_ERROR_NONE) - { - ctx->sp = save_sp; - ctx->hp = save_hp; - } - - ctx->n_idx = n_idx; - - return err; -} - -void BaseContext::SetErrorMessage(const char *msg, va_list ap) -{ - m_CustomMsg = true; - - vsnprintf(m_MsgCache, sizeof(m_MsgCache), msg, ap); -} - -cell_t BaseContext::ThrowNativeErrorEx(int error, const char *msg, ...) -{ - if (!m_InExec) - { - return 0; - } - - ctx->n_err = error; - - if (msg) - { - va_list ap; - va_start(ap, msg); - SetErrorMessage(msg, ap); - va_end(ap); - } - - return 0; -} - -cell_t BaseContext::ThrowNativeError(const char *msg, ...) -{ - if (!m_InExec) - { - return 0; - } - - ctx->n_err = SP_ERROR_NATIVE; - - if (msg) - { - va_list ap; - va_start(ap, msg); - SetErrorMessage(msg, ap); - va_end(ap); - } - - return 0; -} - -int BaseContext::HeapAlloc(unsigned int cells, cell_t *local_addr, cell_t **phys_addr) -{ - cell_t *addr; - ucell_t realmem; - -#if 0 - if (cells > CELLBOUNDMAX) - { - return SP_ERROR_ARAM; - } -#else - assert(cells < CELLBOUNDMAX); -#endif - - realmem = cells * sizeof(cell_t); - - /** - * Check if the space between the heap and stack is sufficient. - */ - if ((cell_t)(ctx->sp - ctx->hp - realmem) < STACKMARGIN) - { - return SP_ERROR_HEAPLOW; - } - - addr = (cell_t *)(ctx->memory + ctx->hp); - /* store size of allocation in cells */ - *addr = (cell_t)cells; - addr++; - ctx->hp += sizeof(cell_t); - - *local_addr = ctx->hp; - - if (phys_addr) - { - *phys_addr = addr; - } - - ctx->hp += realmem; - - return SP_ERROR_NONE; -} - -int BaseContext::HeapPop(cell_t local_addr) -{ - cell_t cellcount; - cell_t *addr; - - /* check the bounds of this address */ - local_addr -= sizeof(cell_t); - if (local_addr < ctx->heap_base || local_addr >= ctx->sp) - { - return SP_ERROR_INVALID_ADDRESS; - } - - addr = (cell_t *)(ctx->memory + local_addr); - cellcount = (*addr) * sizeof(cell_t); - /* check if this memory count looks valid */ - if ((signed)(ctx->hp - cellcount - sizeof(cell_t)) != local_addr) - { - return SP_ERROR_INVALID_ADDRESS; - } - - ctx->hp = local_addr; - - return SP_ERROR_NONE; -} - - -int BaseContext::HeapRelease(cell_t local_addr) -{ - if (local_addr < ctx->heap_base) - { - return SP_ERROR_INVALID_ADDRESS; - } - - ctx->hp = local_addr - sizeof(cell_t); - - return SP_ERROR_NONE; -} - -int BaseContext::FindNativeByName(const char *name, uint32_t *index) -{ - int high; - - high = ctx->plugin->info.natives_num - 1; - - for (uint32_t i=0; iplugin->info.natives_num; i++) - { - if (strcmp(ctx->natives[i].name, name) == 0) - { - if (index) - { - *index = i; - } - return SP_ERROR_NONE; - } - } - - return SP_ERROR_NOT_FOUND; -} - -int BaseContext::GetNativeByIndex(uint32_t index, sp_native_t **native) -{ - if (index >= ctx->plugin->info.natives_num) - { - return SP_ERROR_INDEX; - } - - if (native) - { - *native = &(ctx->natives[index]); - } - - return SP_ERROR_NONE; -} - - -uint32_t BaseContext::GetNativesNum() -{ - return ctx->plugin->info.natives_num; -} - -int BaseContext::FindPublicByName(const char *name, uint32_t *index) -{ - int diff, high, low; - uint32_t mid; - - high = ctx->plugin->info.publics_num - 1; - low = 0; - - while (low <= high) - { - mid = (low + high) / 2; - diff = strcmp(ctx->publics[mid].name, name); - if (diff == 0) - { - if (index) - { - *index = mid; - } - return SP_ERROR_NONE; - } else if (diff < 0) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - return SP_ERROR_NOT_FOUND; -} - -int BaseContext::GetPublicByIndex(uint32_t index, sp_public_t **pblic) -{ - if (index >= ctx->plugin->info.publics_num) - { - return SP_ERROR_INDEX; - } - - if (pblic) - { - *pblic = &(ctx->publics[index]); - } - - return SP_ERROR_NONE; -} - -uint32_t BaseContext::GetPublicsNum() -{ - return ctx->plugin->info.publics_num; -} - -int BaseContext::GetPubvarByIndex(uint32_t index, sp_pubvar_t **pubvar) -{ - if (index >= ctx->plugin->info.pubvars_num) - { - return SP_ERROR_INDEX; - } - - if (pubvar) - { - *pubvar = &(ctx->pubvars[index]); - } - - return SP_ERROR_NONE; -} - -int BaseContext::FindPubvarByName(const char *name, uint32_t *index) -{ - int diff, high, low; - uint32_t mid; - - high = ctx->plugin->info.pubvars_num - 1; - low = 0; - - while (low <= high) - { - mid = (low + high) / 2; - diff = strcmp(ctx->pubvars[mid].name, name); - if (diff == 0) - { - if (index) - { - *index = mid; - } - return SP_ERROR_NONE; - } else if (diff < 0) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - return SP_ERROR_NOT_FOUND; -} - -int BaseContext::GetPubvarAddrs(uint32_t index, cell_t *local_addr, cell_t **phys_addr) -{ - if (index >= ctx->plugin->info.pubvars_num) - { - return SP_ERROR_INDEX; - } - - *local_addr = ctx->plugin->info.pubvars[index].address; - *phys_addr = ctx->pubvars[index].offs; - - return SP_ERROR_NONE; -} - -uint32_t BaseContext::GetPubVarsNum() -{ - return ctx->plugin->info.pubvars_num; -} - -int BaseContext::BindNatives(const sp_nativeinfo_t *natives, unsigned int num, int overwrite) -{ - uint32_t i, j, max; - - max = ctx->plugin->info.natives_num; - - for (i=0; inatives[i].status == SP_NATIVE_BOUND) && !overwrite) - { - continue; - } - - for (j=0; (natives[j].name) && (!num || jnatives[i].name, natives[j].name)) - { - ctx->natives[i].pfn = natives[j].func; - ctx->natives[i].status = SP_NATIVE_BOUND; - } - } - } - - return SP_ERROR_NONE; -} - -int BaseContext::BindNative(const sp_nativeinfo_t *native) -{ - uint32_t index; - int err; - - if ((err = FindNativeByName(native->name, &index)) != SP_ERROR_NONE) - { - return err; - } - - ctx->natives[index].pfn = native->func; - ctx->natives[index].status = SP_NATIVE_BOUND; - - return SP_ERROR_NONE; -} - -int BaseContext::BindNativeToAny(SPVM_NATIVE_FUNC native) -{ - uint32_t nativesnum, i; - - nativesnum = ctx->plugin->info.natives_num; - - for (i=0; inatives[i].status == SP_NATIVE_UNBOUND) - { - ctx->natives[i].pfn = native; - ctx->natives[i].status = SP_NATIVE_BOUND; - } - } - - return SP_ERROR_NONE; -} - -int BaseContext::LocalToPhysAddr(cell_t local_addr, cell_t **phys_addr) -{ - if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) - { - return SP_ERROR_INVALID_ADDRESS; - } - - if (phys_addr) - { - *phys_addr = (cell_t *)(ctx->memory + local_addr); - } - - return SP_ERROR_NONE; -} - -int BaseContext::PushCell(cell_t value) -{ - if ((ctx->hp + STACKMARGIN) > (cell_t)(ctx->sp - sizeof(cell_t))) - { - return SP_ERROR_STACKLOW; - } - - ctx->sp -= sizeof(cell_t); - *(cell_t *)(ctx->memory + ctx->sp) = value; - ctx->pushcount++; - - return SP_ERROR_NONE; -} - -int BaseContext::PushCellsFromArray(cell_t array[], unsigned int numcells) -{ - unsigned int i; - int err; - - for (i=0; isp += (cell_t)(i * sizeof(cell_t)); - ctx->pushcount -= i; - return err; - } - } - - return SP_ERROR_NONE; -} - -int BaseContext::PushCellArray(cell_t *local_addr, cell_t **phys_addr, cell_t array[], unsigned int numcells) -{ - cell_t *ph_addr; - int err; - - if ((err = HeapAlloc(numcells, local_addr, &ph_addr)) != SP_ERROR_NONE) - { - return err; - } - - memcpy(ph_addr, array, numcells * sizeof(cell_t)); - - if ((err = PushCell(*local_addr)) != SP_ERROR_NONE) - { - HeapRelease(*local_addr); - return err; - } - - if (phys_addr) - { - *phys_addr = ph_addr; - } - - return SP_ERROR_NONE; -} - -int BaseContext::LocalToString(cell_t local_addr, char **addr) -{ - if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) - { - return SP_ERROR_INVALID_ADDRESS; - } - *addr = (char *)(ctx->memory + local_addr); - - return SP_ERROR_NONE; -} - -int BaseContext::PushString(cell_t *local_addr, char **phys_addr, const char *string) -{ - char *ph_addr; - int err; - unsigned int len, numcells = ((len=strlen(string)) + sizeof(cell_t)) / sizeof(cell_t); - - if ((err = HeapAlloc(numcells, local_addr, (cell_t **)&ph_addr)) != SP_ERROR_NONE) - { - return err; - } - - memcpy(ph_addr, string, len); - ph_addr[len] = '\0'; - - if ((err = PushCell(*local_addr)) != SP_ERROR_NONE) - { - HeapRelease(*local_addr); - return err; - } - - if (phys_addr) - { - *phys_addr = ph_addr; - } - - return SP_ERROR_NONE; -} - -int BaseContext::StringToLocal(cell_t local_addr, size_t bytes, const char *source) -{ - char *dest; - size_t len; - - if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) - { - return SP_ERROR_INVALID_ADDRESS; - } - - if (bytes == 0) - { - return SP_ERROR_NONE; - } - - len = strlen(source); - dest = (char *)(ctx->memory + local_addr); - - if (len >= bytes) - { - len = bytes - 1; - } - - memcpy(dest, source, len); - dest[len] = '\0'; - - return SP_ERROR_NONE; -} - -inline int __CheckValidChar(char *c) -{ - int count; - int bytecount = 0; - - for (count=1; (*c & 0xC0) == 0x80; count++) - { - c--; - } - - switch (*c & 0xF0) - { - case 0xC0: - case 0xD0: - { - bytecount = 2; - break; - } - case 0xE0: - { - bytecount = 3; - break; - } - case 0xF0: - { - bytecount = 4; - break; - } - } - - if (bytecount != count) - { - return count; - } - - return 0; -} - -int BaseContext::StringToLocalUTF8(cell_t local_addr, size_t maxbytes, const char *source, size_t *wrtnbytes) -{ - char *dest; - size_t len; - bool needtocheck = false; - - if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) - { - return SP_ERROR_INVALID_ADDRESS; - } - - if (maxbytes == 0) - { - return SP_ERROR_NONE; - } - - len = strlen(source); - dest = (char *)(ctx->memory + local_addr); - - if ((size_t)len >= maxbytes) - { - len = maxbytes - 1; - needtocheck = true; - } - - memcpy(dest, source, len); - if ((dest[len-1] & 1<<7) && needtocheck) - { - len -= __CheckValidChar(dest+len-1); - } - dest[len] = '\0'; - - if (wrtnbytes) - { - *wrtnbytes = len; - } - - return SP_ERROR_NONE; -} - -#define USHR(x) ((unsigned int)(x)>>1) - -int BaseContext::LookupFile(ucell_t addr, const char **filename) -{ - int high, low, mid; - - high = ctx->plugin->debug.files_num; - low = -1; - - while (high - low > 1) - { - mid = USHR(low + high); - if (ctx->files[mid].addr <= addr) - { - low = mid; - } else { - high = mid; - } - } - - if (low == -1) - { - return SP_ERROR_NOT_FOUND; - } - - *filename = ctx->files[low].name; - - return SP_ERROR_NONE; -} - -int BaseContext::LookupFunction(ucell_t addr, const char **name) -{ - uint32_t iter, max = ctx->plugin->debug.syms_num; - - for (iter=0; itersymbols[iter].sym->ident == SP_SYM_FUNCTION) - && (ctx->symbols[iter].codestart <= addr) - && (ctx->symbols[iter].codeend > addr)) - { - break; - } - } - - if (iter >= max) - { - return SP_ERROR_NOT_FOUND; - } - - *name = ctx->symbols[iter].name; - - return SP_ERROR_NONE; -} - -int BaseContext::LookupLine(ucell_t addr, uint32_t *line) -{ - int high, low, mid; - - high = ctx->plugin->debug.lines_num; - low = -1; - - while (high - low > 1) - { - mid = USHR(low + high); - if (ctx->lines[mid].addr <= addr) - { - low = mid; - } else { - high = mid; - } - } - - if (low == -1) - { - return SP_ERROR_NOT_FOUND; - } - - /* Since the CIP occurs BEFORE the line, we have to add one */ - *line = ctx->lines[low].line + 1; - - return SP_ERROR_NONE; -} - -IPluginFunction *BaseContext::GetFunctionById(funcid_t func_id) -{ - CFunction *pFunc = NULL; - - if (func_id & 1) - { - func_id >>= 1; - if (func_id >= ctx->plugin->info.publics_num) - { - return NULL; - } - pFunc = m_pub_funcs[func_id]; - if (!pFunc) - { - m_pub_funcs[func_id] = new CFunction(ctx->publics[func_id].code_offs, this); - pFunc = m_pub_funcs[func_id]; - } else if (pFunc->IsInvalidated()) { - pFunc->Set(ctx->publics[func_id].code_offs, this); - } - } else { - /* :TODO: currently not used */ -#if 0 - func_id >>= 1; - unsigned int index; - if (!g_pVM->FunctionLookup(ctx, func_id, &index)) - { - return NULL; - } - pFunc = m_priv_funcs[func_id]; - if (!pFunc) - { - m_priv_funcs[func_id] = new CFunction(save, this); - pFunc = m_priv_funcs[func_id]; - } -#endif - assert(false); - } - - return pFunc; -} - -IPluginFunction *BaseContext::GetFunctionByName(const char *public_name) -{ - uint32_t index; - - if (FindPublicByName(public_name, &index) != SP_ERROR_NONE) - { - return NULL; - } - - CFunction *pFunc = m_pub_funcs[index]; - if (!pFunc) - { - sp_public_t *pub = NULL; - GetPublicByIndex(index, &pub); - if (pub) - { - m_pub_funcs[index] = new CFunction(pub->code_offs, this); - } - pFunc = m_pub_funcs[index]; - } else if (pFunc->IsInvalidated()) { - sp_public_t *pub = NULL; - GetPublicByIndex(index, &pub); - if (pub) - { - pFunc->Set(pub->code_offs, this); - } else { - pFunc = NULL; - } - } - - return pFunc; -} - -int BaseContext::LocalToStringNULL(cell_t local_addr, char **addr) -{ - int err; - if ((err = LocalToString(local_addr, addr)) != SP_ERROR_NONE) - { - return err; - } - - if ((cell_t *)*addr == m_pNullString) - { - *addr = NULL; - } - - return SP_ERROR_NONE; -} - -#if defined SOURCEMOD_BUILD -SourceMod::IdentityToken_t *BaseContext::GetIdentity() -{ - return m_pToken; -} - -void BaseContext::SetIdentity(SourceMod::IdentityToken_t *token) -{ - m_pToken = token; -} - -cell_t *BaseContext::GetNullRef(SP_NULL_TYPE type) -{ - if (type == SP_NULL_VECTOR) - { - return m_pNullVec; - } - - return NULL; -} -#endif + */ + +#include +#include +#include +#include +#include "sp_vm_api.h" +#include "sp_vm_basecontext.h" +#include "sp_vm_engine.h" + +#ifdef SOURCEMOD_BUILD +#include "Logger.h" +#endif + +using namespace SourcePawn; + +extern SourcePawnEngine g_SourcePawn; + +#define CELLBOUNDMAX (INT_MAX/sizeof(cell_t)) +#define STACKMARGIN ((cell_t)(16*sizeof(cell_t))) + +int GlobalDebugBreak(sp_context_t *ctx, uint32_t frm, uint32_t cip) +{ + g_SourcePawn.RunTracer(ctx, frm, cip); + + return SP_ERROR_NONE; +} + +BaseContext::BaseContext(sp_context_t *_ctx) +{ + ctx = _ctx; + ctx->context = this; + ctx->dbreak = GlobalDebugBreak; + m_InExec = false; + m_CustomMsg = false; + m_funcsnum = ctx->vmbase->FunctionCount(ctx); +#if 0 + m_priv_funcs = NULL; +#endif + m_pub_funcs = NULL; + +#if 0 + /** + * Note: Since the m_plugin member will never change, + * it is safe to assume the function count will never change + */ + if (m_funcsnum && m_priv_funcs == NULL) + { + m_priv_funcs = new CFunction *[m_funcsnum]; + memset(m_priv_funcs, 0, sizeof(CFunction *) * m_funcsnum); + } else { + m_priv_funcs = NULL; + } +#endif + + if (ctx->plugin->info.publics_num && m_pub_funcs == NULL) + { + m_pub_funcs = new CFunction *[ctx->plugin->info.publics_num]; + memset(m_pub_funcs, 0, sizeof(CFunction *) * ctx->plugin->info.publics_num); + } else { + m_pub_funcs = NULL; + } + + /* Initialize the null references */ + uint32_t index; + if (FindPubvarByName("NULL_VECTOR", &index) == SP_ERROR_NONE) + { + sp_pubvar_t *pubvar; + GetPubvarByIndex(index, &pubvar); + m_pNullVec = pubvar->offs; + } else { + m_pNullVec = NULL; + } + + if (FindPubvarByName("NULL_STRING", &index) == SP_ERROR_NONE) + { + sp_pubvar_t *pubvar; + GetPubvarByIndex(index, &pubvar); + m_pNullString = pubvar->offs; + } else { + m_pNullString = NULL; + } +} + +void BaseContext::FlushFunctionCache() +{ + if (m_pub_funcs) + { + for (uint32_t i=0; iplugin->info.publics_num; i++) + { + delete m_pub_funcs[i]; + m_pub_funcs[i] = NULL; + } + } + +#if 0 + if (m_priv_funcs) + { + for (unsigned int i=0; iplugin->info.publics_num; i++) + { + if (!m_pub_funcs[i]) + { + continue; + } + if (GetPublicByIndex(i, &pub) != SP_ERROR_NONE) + { + continue; + } + m_pub_funcs[i]->Set(pub->code_offs, this); + } + } + +#if 0 + if (m_priv_funcs) + { + for (unsigned int i=0; i + } + } +#endif +} + +BaseContext::~BaseContext() +{ + FlushFunctionCache(); + delete [] m_pub_funcs; + m_pub_funcs = NULL; +#if 0 + delete [] m_priv_funcs; + m_priv_funcs = NULL; +#endif +} + +void BaseContext::SetContext(sp_context_t *_ctx) +{ + if (!_ctx) + { + return; + } + ctx = _ctx; + ctx->context = this; + ctx->dbreak = GlobalDebugBreak; + RefreshFunctionCache(); +} + +IVirtualMachine *BaseContext::GetVirtualMachine() +{ + return (IVirtualMachine *)ctx->vmbase; +} + +sp_context_t *BaseContext::GetContext() +{ + return ctx; +} + +bool BaseContext::IsDebugging() +{ + return (ctx->flags & SPFLAG_PLUGIN_DEBUG); +} + +int BaseContext::SetDebugBreak(SPVM_DEBUGBREAK newpfn, SPVM_DEBUGBREAK *oldpfn) +{ + if (!IsDebugging()) + { + return SP_ERROR_NOTDEBUGGING; + } + + *oldpfn = ctx->dbreak; + ctx->dbreak = newpfn; + + return SP_ERROR_NONE; +} + +IPluginDebugInfo *BaseContext::GetDebugInfo() +{ + return this; +} + +int BaseContext::Execute(uint32_t code_addr, cell_t *result) +{ + if ((ctx->flags & SPFLAG_PLUGIN_PAUSED) == SPFLAG_PLUGIN_PAUSED) + { + 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; + int err; + + PushCell(pushcount++); + ctx->pushcount = 0; + + cell_t save_sp = ctx->sp; + cell_t save_hp = ctx->hp; + uint32_t n_idx = ctx->n_idx; + + bool wasExec = m_InExec; + + /* Clear the error state, if any */ + ctx->n_err = SP_ERROR_NONE; + ctx->n_idx = 0; + m_InExec = true; + m_MsgCache[0] = '\0'; + m_CustomMsg = false; + + g_SourcePawn.PushTracer(ctx); + + err = vm->ContextExecute(ctx, code_addr, result); + + m_InExec = wasExec; + + /** + * :TODO: Calling from a plugin in here will erase the cached message... + * Should that be documented? + */ + g_SourcePawn.PopTracer(err, m_CustomMsg ? m_MsgCache : NULL); + +#if 1//defined _DEBUG + //:TODO: debug code for leak detection, remove before the release? + if (err == SP_ERROR_NONE) + { + if ((ctx->sp - (cell_t)(pushcount * sizeof(cell_t))) != save_sp) + { + const char *name; + ctx->context->GetDebugInfo()->LookupFunction(code_addr, &name); + g_Logger.LogError("Stack leak detected: sp:%d should be %d on function %s", ctx->sp, save_sp, name); + } + if (ctx->hp != save_hp) + { + const char *name; + ctx->context->GetDebugInfo()->LookupFunction(code_addr, &name); + g_Logger.LogError("Heap leak detected: hp:%d should be %d on function %s", ctx->hp, save_hp, name); + } + //assert(ctx->sp - pushcount * sizeof(cell_t) == save_sp); + //assert(ctx->hp == save_hp); + } +#endif + if (err != SP_ERROR_NONE) + { + ctx->sp = save_sp; + ctx->hp = save_hp; + } + + ctx->n_idx = n_idx; + + return err; +} + +void BaseContext::SetErrorMessage(const char *msg, va_list ap) +{ + m_CustomMsg = true; + + vsnprintf(m_MsgCache, sizeof(m_MsgCache), msg, ap); +} + +cell_t BaseContext::ThrowNativeErrorEx(int error, const char *msg, ...) +{ + if (!m_InExec) + { + return 0; + } + + ctx->n_err = error; + + if (msg) + { + va_list ap; + va_start(ap, msg); + SetErrorMessage(msg, ap); + va_end(ap); + } + + return 0; +} + +cell_t BaseContext::ThrowNativeError(const char *msg, ...) +{ + if (!m_InExec) + { + return 0; + } + + ctx->n_err = SP_ERROR_NATIVE; + + if (msg) + { + va_list ap; + va_start(ap, msg); + SetErrorMessage(msg, ap); + va_end(ap); + } + + return 0; +} + +int BaseContext::HeapAlloc(unsigned int cells, cell_t *local_addr, cell_t **phys_addr) +{ + cell_t *addr; + ucell_t realmem; + +#if 0 + if (cells > CELLBOUNDMAX) + { + return SP_ERROR_ARAM; + } +#else + assert(cells < CELLBOUNDMAX); +#endif + + realmem = cells * sizeof(cell_t); + + /** + * Check if the space between the heap and stack is sufficient. + */ + if ((cell_t)(ctx->sp - ctx->hp - realmem) < STACKMARGIN) + { + return SP_ERROR_HEAPLOW; + } + + addr = (cell_t *)(ctx->memory + ctx->hp); + /* store size of allocation in cells */ + *addr = (cell_t)cells; + addr++; + ctx->hp += sizeof(cell_t); + + *local_addr = ctx->hp; + + if (phys_addr) + { + *phys_addr = addr; + } + + ctx->hp += realmem; + + return SP_ERROR_NONE; +} + +int BaseContext::HeapPop(cell_t local_addr) +{ + cell_t cellcount; + cell_t *addr; + + /* check the bounds of this address */ + local_addr -= sizeof(cell_t); + if (local_addr < ctx->heap_base || local_addr >= ctx->sp) + { + return SP_ERROR_INVALID_ADDRESS; + } + + addr = (cell_t *)(ctx->memory + local_addr); + cellcount = (*addr) * sizeof(cell_t); + /* check if this memory count looks valid */ + if ((signed)(ctx->hp - cellcount - sizeof(cell_t)) != local_addr) + { + return SP_ERROR_INVALID_ADDRESS; + } + + ctx->hp = local_addr; + + return SP_ERROR_NONE; +} + + +int BaseContext::HeapRelease(cell_t local_addr) +{ + if (local_addr < ctx->heap_base) + { + return SP_ERROR_INVALID_ADDRESS; + } + + ctx->hp = local_addr - sizeof(cell_t); + + return SP_ERROR_NONE; +} + +int BaseContext::FindNativeByName(const char *name, uint32_t *index) +{ + int high; + + high = ctx->plugin->info.natives_num - 1; + + for (uint32_t i=0; iplugin->info.natives_num; i++) + { + if (strcmp(ctx->natives[i].name, name) == 0) + { + if (index) + { + *index = i; + } + return SP_ERROR_NONE; + } + } + + return SP_ERROR_NOT_FOUND; +} + +int BaseContext::GetNativeByIndex(uint32_t index, sp_native_t **native) +{ + if (index >= ctx->plugin->info.natives_num) + { + return SP_ERROR_INDEX; + } + + if (native) + { + *native = &(ctx->natives[index]); + } + + return SP_ERROR_NONE; +} + + +uint32_t BaseContext::GetNativesNum() +{ + return ctx->plugin->info.natives_num; +} + +int BaseContext::FindPublicByName(const char *name, uint32_t *index) +{ + int diff, high, low; + uint32_t mid; + + high = ctx->plugin->info.publics_num - 1; + low = 0; + + while (low <= high) + { + mid = (low + high) / 2; + diff = strcmp(ctx->publics[mid].name, name); + if (diff == 0) + { + if (index) + { + *index = mid; + } + return SP_ERROR_NONE; + } else if (diff < 0) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return SP_ERROR_NOT_FOUND; +} + +int BaseContext::GetPublicByIndex(uint32_t index, sp_public_t **pblic) +{ + if (index >= ctx->plugin->info.publics_num) + { + return SP_ERROR_INDEX; + } + + if (pblic) + { + *pblic = &(ctx->publics[index]); + } + + return SP_ERROR_NONE; +} + +uint32_t BaseContext::GetPublicsNum() +{ + return ctx->plugin->info.publics_num; +} + +int BaseContext::GetPubvarByIndex(uint32_t index, sp_pubvar_t **pubvar) +{ + if (index >= ctx->plugin->info.pubvars_num) + { + return SP_ERROR_INDEX; + } + + if (pubvar) + { + *pubvar = &(ctx->pubvars[index]); + } + + return SP_ERROR_NONE; +} + +int BaseContext::FindPubvarByName(const char *name, uint32_t *index) +{ + int diff, high, low; + uint32_t mid; + + high = ctx->plugin->info.pubvars_num - 1; + low = 0; + + while (low <= high) + { + mid = (low + high) / 2; + diff = strcmp(ctx->pubvars[mid].name, name); + if (diff == 0) + { + if (index) + { + *index = mid; + } + return SP_ERROR_NONE; + } else if (diff < 0) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return SP_ERROR_NOT_FOUND; +} + +int BaseContext::GetPubvarAddrs(uint32_t index, cell_t *local_addr, cell_t **phys_addr) +{ + if (index >= ctx->plugin->info.pubvars_num) + { + return SP_ERROR_INDEX; + } + + *local_addr = ctx->plugin->info.pubvars[index].address; + *phys_addr = ctx->pubvars[index].offs; + + return SP_ERROR_NONE; +} + +uint32_t BaseContext::GetPubVarsNum() +{ + return ctx->plugin->info.pubvars_num; +} + +int BaseContext::BindNatives(const sp_nativeinfo_t *natives, unsigned int num, int overwrite) +{ + uint32_t i, j, max; + + max = ctx->plugin->info.natives_num; + + for (i=0; inatives[i].status == SP_NATIVE_BOUND) && !overwrite) + { + continue; + } + + for (j=0; (natives[j].name) && (!num || jnatives[i].name, natives[j].name)) + { + ctx->natives[i].pfn = natives[j].func; + ctx->natives[i].status = SP_NATIVE_BOUND; + } + } + } + + return SP_ERROR_NONE; +} + +int BaseContext::BindNative(const sp_nativeinfo_t *native) +{ + uint32_t index; + int err; + + if ((err = FindNativeByName(native->name, &index)) != SP_ERROR_NONE) + { + return err; + } + + ctx->natives[index].pfn = native->func; + ctx->natives[index].status = SP_NATIVE_BOUND; + + return SP_ERROR_NONE; +} + +int BaseContext::BindNativeToAny(SPVM_NATIVE_FUNC native) +{ + uint32_t nativesnum, i; + + nativesnum = ctx->plugin->info.natives_num; + + for (i=0; inatives[i].status == SP_NATIVE_UNBOUND) + { + ctx->natives[i].pfn = native; + ctx->natives[i].status = SP_NATIVE_BOUND; + } + } + + return SP_ERROR_NONE; +} + +int BaseContext::LocalToPhysAddr(cell_t local_addr, cell_t **phys_addr) +{ + if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) + { + return SP_ERROR_INVALID_ADDRESS; + } + + if (phys_addr) + { + *phys_addr = (cell_t *)(ctx->memory + local_addr); + } + + return SP_ERROR_NONE; +} + +int BaseContext::PushCell(cell_t value) +{ + if ((ctx->hp + STACKMARGIN) > (cell_t)(ctx->sp - sizeof(cell_t))) + { + return SP_ERROR_STACKLOW; + } + + ctx->sp -= sizeof(cell_t); + *(cell_t *)(ctx->memory + ctx->sp) = value; + ctx->pushcount++; + + return SP_ERROR_NONE; +} + +int BaseContext::PushCellsFromArray(cell_t array[], unsigned int numcells) +{ + unsigned int i; + int err; + + for (i=0; isp += (cell_t)(i * sizeof(cell_t)); + ctx->pushcount -= i; + return err; + } + } + + return SP_ERROR_NONE; +} + +int BaseContext::PushCellArray(cell_t *local_addr, cell_t **phys_addr, cell_t array[], unsigned int numcells) +{ + cell_t *ph_addr; + int err; + + if ((err = HeapAlloc(numcells, local_addr, &ph_addr)) != SP_ERROR_NONE) + { + return err; + } + + memcpy(ph_addr, array, numcells * sizeof(cell_t)); + + if ((err = PushCell(*local_addr)) != SP_ERROR_NONE) + { + HeapRelease(*local_addr); + return err; + } + + if (phys_addr) + { + *phys_addr = ph_addr; + } + + return SP_ERROR_NONE; +} + +int BaseContext::LocalToString(cell_t local_addr, char **addr) +{ + if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) + { + return SP_ERROR_INVALID_ADDRESS; + } + *addr = (char *)(ctx->memory + local_addr); + + return SP_ERROR_NONE; +} + +int BaseContext::PushString(cell_t *local_addr, char **phys_addr, const char *string) +{ + char *ph_addr; + int err; + unsigned int len, numcells = ((len=strlen(string)) + sizeof(cell_t)) / sizeof(cell_t); + + if ((err = HeapAlloc(numcells, local_addr, (cell_t **)&ph_addr)) != SP_ERROR_NONE) + { + return err; + } + + memcpy(ph_addr, string, len); + ph_addr[len] = '\0'; + + if ((err = PushCell(*local_addr)) != SP_ERROR_NONE) + { + HeapRelease(*local_addr); + return err; + } + + if (phys_addr) + { + *phys_addr = ph_addr; + } + + return SP_ERROR_NONE; +} + +int BaseContext::StringToLocal(cell_t local_addr, size_t bytes, const char *source) +{ + char *dest; + size_t len; + + if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) + { + return SP_ERROR_INVALID_ADDRESS; + } + + if (bytes == 0) + { + return SP_ERROR_NONE; + } + + len = strlen(source); + dest = (char *)(ctx->memory + local_addr); + + if (len >= bytes) + { + len = bytes - 1; + } + + memcpy(dest, source, len); + dest[len] = '\0'; + + return SP_ERROR_NONE; +} + +inline int __CheckValidChar(char *c) +{ + int count; + int bytecount = 0; + + for (count=1; (*c & 0xC0) == 0x80; count++) + { + c--; + } + + switch (*c & 0xF0) + { + case 0xC0: + case 0xD0: + { + bytecount = 2; + break; + } + case 0xE0: + { + bytecount = 3; + break; + } + case 0xF0: + { + bytecount = 4; + break; + } + } + + if (bytecount != count) + { + return count; + } + + return 0; +} + +int BaseContext::StringToLocalUTF8(cell_t local_addr, size_t maxbytes, const char *source, size_t *wrtnbytes) +{ + char *dest; + size_t len; + bool needtocheck = false; + + if (((local_addr >= ctx->hp) && (local_addr < ctx->sp)) || (local_addr < 0) || ((ucell_t)local_addr >= ctx->mem_size)) + { + return SP_ERROR_INVALID_ADDRESS; + } + + if (maxbytes == 0) + { + return SP_ERROR_NONE; + } + + len = strlen(source); + dest = (char *)(ctx->memory + local_addr); + + if ((size_t)len >= maxbytes) + { + len = maxbytes - 1; + needtocheck = true; + } + + memcpy(dest, source, len); + if ((dest[len-1] & 1<<7) && needtocheck) + { + len -= __CheckValidChar(dest+len-1); + } + dest[len] = '\0'; + + if (wrtnbytes) + { + *wrtnbytes = len; + } + + return SP_ERROR_NONE; +} + +#define USHR(x) ((unsigned int)(x)>>1) + +int BaseContext::LookupFile(ucell_t addr, const char **filename) +{ + int high, low, mid; + + high = ctx->plugin->debug.files_num; + low = -1; + + while (high - low > 1) + { + mid = USHR(low + high); + if (ctx->files[mid].addr <= addr) + { + low = mid; + } else { + high = mid; + } + } + + if (low == -1) + { + return SP_ERROR_NOT_FOUND; + } + + *filename = ctx->files[low].name; + + return SP_ERROR_NONE; +} + +int BaseContext::LookupFunction(ucell_t addr, const char **name) +{ + uint32_t iter, max = ctx->plugin->debug.syms_num; + + for (iter=0; itersymbols[iter].sym->ident == SP_SYM_FUNCTION) + && (ctx->symbols[iter].codestart <= addr) + && (ctx->symbols[iter].codeend > addr)) + { + break; + } + } + + if (iter >= max) + { + return SP_ERROR_NOT_FOUND; + } + + *name = ctx->symbols[iter].name; + + return SP_ERROR_NONE; +} + +int BaseContext::LookupLine(ucell_t addr, uint32_t *line) +{ + int high, low, mid; + + high = ctx->plugin->debug.lines_num; + low = -1; + + while (high - low > 1) + { + mid = USHR(low + high); + if (ctx->lines[mid].addr <= addr) + { + low = mid; + } else { + high = mid; + } + } + + if (low == -1) + { + return SP_ERROR_NOT_FOUND; + } + + /* Since the CIP occurs BEFORE the line, we have to add one */ + *line = ctx->lines[low].line + 1; + + return SP_ERROR_NONE; +} + +IPluginFunction *BaseContext::GetFunctionById(funcid_t func_id) +{ + CFunction *pFunc = NULL; + + if (func_id & 1) + { + func_id >>= 1; + if (func_id >= ctx->plugin->info.publics_num) + { + return NULL; + } + pFunc = m_pub_funcs[func_id]; + if (!pFunc) + { + m_pub_funcs[func_id] = new CFunction(ctx->publics[func_id].code_offs, this); + pFunc = m_pub_funcs[func_id]; + } else if (pFunc->IsInvalidated()) { + pFunc->Set(ctx->publics[func_id].code_offs, this); + } + } else { + /* :TODO: currently not used */ +#if 0 + func_id >>= 1; + unsigned int index; + if (!g_pVM->FunctionLookup(ctx, func_id, &index)) + { + return NULL; + } + pFunc = m_priv_funcs[func_id]; + if (!pFunc) + { + m_priv_funcs[func_id] = new CFunction(save, this); + pFunc = m_priv_funcs[func_id]; + } +#endif + assert(false); + } + + return pFunc; +} + +IPluginFunction *BaseContext::GetFunctionByName(const char *public_name) +{ + uint32_t index; + + if (FindPublicByName(public_name, &index) != SP_ERROR_NONE) + { + return NULL; + } + + CFunction *pFunc = m_pub_funcs[index]; + if (!pFunc) + { + sp_public_t *pub = NULL; + GetPublicByIndex(index, &pub); + if (pub) + { + m_pub_funcs[index] = new CFunction(pub->code_offs, this); + } + pFunc = m_pub_funcs[index]; + } else if (pFunc->IsInvalidated()) { + sp_public_t *pub = NULL; + GetPublicByIndex(index, &pub); + if (pub) + { + pFunc->Set(pub->code_offs, this); + } else { + pFunc = NULL; + } + } + + return pFunc; +} + +int BaseContext::LocalToStringNULL(cell_t local_addr, char **addr) +{ + int err; + if ((err = LocalToString(local_addr, addr)) != SP_ERROR_NONE) + { + return err; + } + + if ((cell_t *)*addr == m_pNullString) + { + *addr = NULL; + } + + return SP_ERROR_NONE; +} + +#if defined SOURCEMOD_BUILD +SourceMod::IdentityToken_t *BaseContext::GetIdentity() +{ + return m_pToken; +} + +void BaseContext::SetIdentity(SourceMod::IdentityToken_t *token) +{ + m_pToken = token; +} + +cell_t *BaseContext::GetNullRef(SP_NULL_TYPE type) +{ + if (type == SP_NULL_VECTOR) + { + return m_pNullVec; + } + + return NULL; +} +#endif diff --git a/core/vm/sp_vm_engine.cpp b/core/vm/sp_vm_engine.cpp index 01d4d34b..b107ba4f 100644 --- a/core/vm/sp_vm_engine.cpp +++ b/core/vm/sp_vm_engine.cpp @@ -81,6 +81,16 @@ static const char *g_ErrorMsgTable[] = "Call was aborted", }; +const char *GetSourcePawnErrorMessage(int error) +{ + if (error < 1 || error > ERROR_MESSAGE_MAX) + { + return NULL; + } + + return g_ErrorMsgTable[error]; +} + SourcePawnEngine::SourcePawnEngine() { m_pDebugHook = NULL; diff --git a/core/vm/sp_vm_engine.h b/core/vm/sp_vm_engine.h index b8d599cd..5aa73d80 100644 --- a/core/vm/sp_vm_engine.h +++ b/core/vm/sp_vm_engine.h @@ -45,7 +45,6 @@ struct TracedCall unsigned int chain; }; - class CContextTrace : public IContextTrace { public: diff --git a/plugins/include/menus.inc b/plugins/include/menus.inc index 20e6b4a7..f7c5515f 100644 --- a/plugins/include/menus.inc +++ b/plugins/include/menus.inc @@ -68,6 +68,11 @@ enum MenuAction #define MENUFLAG_BUTTON_EXITBACK (1<<1) /**< Menu has an "exit back" button */ #define MENUFLAG_NO_SOUND (1<<2) /**< Menu will not have any select sounds */ +#define VOTEINFO_CLIENT_INDEX 0 /**< Client index */ +#define VOTEINFO_CLIENT_ITEM 1 /**< Item the client selected, or -1 for none */ +#define VOTEINFO_ITEM_INDEX 0 /**< Item index */ +#define VOTEINFO_ITEM_VOTES 1 /**< Number of votes for the item */ + /** * Reasons a menu can be cancelled. */ @@ -351,12 +356,20 @@ native GetMenuOptionFlags(Handle:menu); native SetMenuOptionFlags(Handle:menu, flags); /** - * Returns whether a vote is in progress on the given menu. + * Returns whether a vote is in progress. * - * @param menu Menu Handle. + * @param menu Deprecated; no longer used. * @return True if a vote is in progress, false otherwise. */ -native bool:IsVoteInProgress(Handle:menu); +native bool:IsVoteInProgress(Handle:menu=INVALID_HANDLE); + +/** + * Cancels the vote in progress. + * + * @noreturn + * @error If no vote is in progress. + */ +native CancelVote(); /** * Broadcasts a menu to a list of clients. The most selected item will be @@ -372,7 +385,7 @@ native bool:IsVoteInProgress(Handle:menu); * @param time Maximum time to leave menu on the screen. * @return True on success, false if this menu already has a vote session * in progress. - * @error Invalid Handle. + * @error Invalid Handle, or a vote is already in progress. */ native bool:VoteMenu(Handle:menu, clients[], numClients, time); @@ -402,6 +415,33 @@ stock VoteMenuToAll(Handle:menu, time) return VoteMenu(menu, players, total, time); } +/** + * Callback for when a vote has ended and results are available. + * + * @param num_votes Number of votes tallied in total. + * @param num_clients Number of clients who could vote. + * @param client_info Array of clients. Use VOTEINFO_CLIENT_ defines. + * @param num_items Number of unique items that were selected. + * @param item_info Array of items, sorted by count. Use VOTEINFO_ITEM + * defines. + * @noreturn + */ +functag VoteHandler public(num_votes, + num_clients, + const client_info[][2], + num_items, + const item_info[][2]); + +/** + * Sets an advanced vote handling callback. If this callback is set, + * MenuAction_VoteEnd will not be called. + * + * @param menu Menu Handle. + * @param callback Callback function. + * @noreturn + * @error Invalid Handle or callback. + */ +native SetVoteResultCallback(Handle:menu, VoteHandler:callback); /** * Returns a style's global Handle. diff --git a/public/IMenuManager.h b/public/IMenuManager.h index 7bbf954e..99622d48 100644 --- a/public/IMenuManager.h +++ b/public/IMenuManager.h @@ -37,7 +37,7 @@ #include #define SMINTERFACE_MENUMANAGER_NAME "IMenuManager" -#define SMINTERFACE_MENUMANAGER_VERSION 8 +#define SMINTERFACE_MENUMANAGER_VERSION 9 /** * @file IMenuManager.h @@ -117,6 +117,27 @@ namespace SourceMod unsigned int access; /**< Access flags required to see */ }; + /** + * @brief Contains information about a vote result. + */ + struct menu_vote_result_t + { + unsigned int num_clients; /**< Number of clients the menu was displayed to */ + unsigned int num_votes; /**< Number of votes received */ + struct menu_client_vote_t + { + int client; /**< Client index */ + int item; /**< Item # (or -1 for none) */ + } *client_list; /**< Array of size num_clients */ + unsigned int num_items; /**< Number of items voted for */ + struct menu_item_vote_t + { + unsigned int item; /**< Item index */ + unsigned int count; /**< Number of votes */ + } *item_list; /**< Array of size num_items, sorted by count, + descending */ + }; + /** * @brief Reasons for a menu dying. */ @@ -141,6 +162,7 @@ namespace SourceMod MenuEnd_Cancelled = -3, /**< Menu was uncleanly cancelled */ MenuEnd_Exit = -4, /**< Menu was cleanly exited via "exit" */ MenuEnd_ExitBack = -5, /**< Menu was cleanly exited via "back" */ + MenuEnd_NoVotes = -6, /**< No votes received */ }; @@ -478,32 +500,15 @@ namespace SourceMod */ virtual IMenuPanel *CreatePanel() =0; - /** - * @brief Returns whether or not the menu should have an "Exit" button for - * paginated menus. - * - * @return True to have an exit button, false otherwise. - */ - virtual bool GetExitButton() =0; - - /** - * @brief Sets whether or not the menu should have an "Exit" button for - * paginated menus. - * - * @param set True to enable, false to disable the exit button. - * @return True on success, false if the exit button is - * non-optional. - */ - virtual bool SetExitButton(bool set) =0; - /** * @brief Sends the menu to a client. * * @param client Client index to display to. * @param time Time to hold menu for. + * @param alt_handler Alternate IMenuHandler. * @return True on success, false otherwise. */ - virtual bool Display(int client, unsigned int time) =0; + virtual bool Display(int client, unsigned int time, IMenuHandler *alt_handler=NULL) =0; /** * @brief Destroys the menu and frees all associated resources. @@ -531,46 +536,6 @@ namespace SourceMod * @return Handle_t handle value. */ virtual Handle_t GetHandle() =0; - - /** - * @brief Sends a menu to multiple clients as a vote menu. All callbacks - * will be sent as normal, except two extras, OnMenuVoteStart and - * OnMenuVoteEnd, will be called. - * - * @param clients Array of client indexes. - * @param numClients Number of client indexes in the array. - * @param maxTime Maximum amount of time to hold the vote. - * @param flags Optional voting flags (currently unused). - * @return True on success, false if a vote is already in - * progress (the menu must be cancelled first). - */ - virtual bool BroadcastVote(int clients[], - unsigned int numClients, - unsigned int maxTime, - unsigned int flags=0) =0; - - /** - * @brief Returns whether a vote menu is active. - * - * @return True if a vote menu is active, false otherwise. - */ - virtual bool IsVoteInProgress() =0; - - /** - * @brief Returns whether to draw a "Back" button on the first page. - * ExitBack buttons are disabled by default. - * - * @return True if enabled, false otherwise. - */ - virtual bool GetExitBackButton() =0; - - /** - * @brief Sets whether to draw a "Back" button on the first page. - * ExitBack buttons are disabled by default. - * - * @param set True to enable, false to disable. - */ - virtual void SetExitBackButton(bool set) =0; /** * @brief Returns menu option flags. @@ -585,6 +550,13 @@ namespace SourceMod * @param flags Menu option flags. */ virtual void SetMenuOptionFlags(unsigned int flags) =0; + + /** + * @brief Returns the menu's handler. + * + * @return IMenuHandler of the menu. + */ + virtual IMenuHandler *GetHandler() =0; }; /** @@ -716,20 +688,15 @@ namespace SourceMod * while it is in this function. * * @param menu Menu pointer. - * @param item Item position that was chosen by a majority. - * @param winningVotes Number of votes from the winning item. - * @param totalVotes Number of votes total. + * @param results Menu vote results. */ - virtual void OnMenuVoteEnd(IBaseMenu *menu, - unsigned int item, - unsigned int winningVotes, - unsigned int totalVotes) + virtual void OnMenuVoteResults(IBaseMenu *menu, const menu_vote_result_t *results) { } /** * @brief Called when a vote is cancelled. If this is called, then - * OnMenuVoteEnd() will not be called. In both cases, OnMenuEnd will + * OnMenuVoteResults() will not be called. In both cases, OnMenuEnd will * always be called. * * @param menu Menu pointer. @@ -737,43 +704,18 @@ namespace SourceMod virtual void OnMenuVoteCancel(IBaseMenu *menu) { } - }; - /** - * @brief Contains functions for managing a vote handler. - */ - class IVoteMenuHandler : public IMenuHandler - { - public: /** - * @brief Returns whether or not a vote is in progress. + * @brief Call to set private handler stuff. * - * @return True if a vote is in progress, false otherwise. + * @param option Option name. + * @param data Private data. + * @return True if set, false if invalid or unrecognized. */ - virtual bool IsVoteInProgress() =0; - - /** - * @brief Use this to mark the vote as in progress (start). - * - * @param menu Menu pointer. - */ - virtual void InitializeVoting(IBaseMenu *menu) =0; - - /** - * @brief Use this to notify that all clients' displays have been - * processed (i.e., there are no more clients to display to). - */ - virtual void StartVoting() =0; - - /** - * @brief Notifies the vote handler that the voting should be - * cancelled. - * - * Cancellation is not immediate and will only occur once every menu - * has been cancelled from clients. Thus this should only be called - * from the beginning of IBaseMenu::Cancel. - */ - virtual void CancelVoting() =0; + virtual bool OnSetHandlerOption(const char *option, const void *data) + { + return false; + } }; /** @@ -792,7 +734,7 @@ namespace SourceMod } virtual bool IsVersionCompatible(unsigned int version) { - if (version < 7 || version > GetInterfaceVersion()) + if (version < 9 || version > GetInterfaceVersion()) { return false; } @@ -815,7 +757,7 @@ namespace SourceMod virtual IMenuStyle *GetDefaultStyle() =0; /** - * @brief Given a set of menu states, converts it to an IDisplay object. + * @brief Given a set of menu states, converts it to an IMenuPanel object. * * The state parameter is both INPUT and OUTPUT. * INPUT: menu, mh, firstItem, lastItem @@ -823,7 +765,7 @@ namespace SourceMod * * @param client Client index. * @param states Menu states. - * @return IDisplay pointer, or NULL if no items could be + * @return IMenuPanel pointer, or NULL if no items could be * found in the IBaseMenu pointer, or NULL if any * other error occurred. Any valid pointer must * be freed using IMenuPanel::DeleteThis. @@ -831,25 +773,40 @@ namespace SourceMod virtual IMenuPanel *RenderMenu(int client, menu_states_t &states, ItemOrder order) =0; /** - * @brief Creates a standard voting wrapper. The wrapper is not - * re-entrant; a second menu cannot be displayed on the same handler - * at the same time. + * @brief Cancels a menu. Calls IBaseMenu::Cancel() after doing some preparatory + * work. This should always be used instead of directly calling Cancel(). * - * @param mh Menu handler to wrap around. - * @return An IMenuHandler pointer that is a wrapper - * around IMenuHandler callbacks to invoke - * voting related callbacks. + * @param menu IBaseMenu pointer. */ - virtual IVoteMenuHandler *CreateVoteWrapper(IMenuHandler *mh) =0; + virtual void CancelMenu(IBaseMenu *menu) =0; /** - * @brief Frees a standard voting wrapper. + * @brief Displays a menu as a vote. * - * @param mh Menu handler pointer created by - * CreateVoteWrapper(). NULL values will be - * safely ignored. + * @param menu IBaseMenu pointer. + * @param num_clients Number of clients to display to. + * @param clients Client index array. + * @param max_time Maximum time to hold menu for. + * @param flags Vote flags (currently unused). + * @return True on success, false if a vote is in progress. */ - virtual void ReleaseVoteWrapper(IVoteMenuHandler *mh) =0; + virtual bool StartVote(IBaseMenu *menu, + unsigned int num_clients, + int clients[], + unsigned int max_time, + unsigned int flags=0) =0; + + /** + * @brief Returns whether or not a vote is in progress. + * + * @return True if a vote is in progress, false otherwise. + */ + virtual bool IsVoteInProgress() =0; + + /** + * @brief Cancels the vote in progress. This calls IBaseMenu::Cancel(). + */ + virtual void CancelVoting() =0; }; }