- 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
This commit is contained in:
David Anderson 2007-08-08 02:27:10 +00:00
parent 9d0c2ea4d8
commit 6d60bd8de2
20 changed files with 1900 additions and 1600 deletions

View File

@ -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;

View File

@ -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);
};

View File

@ -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 \

View File

@ -34,6 +34,7 @@
#include <stdarg.h>
#include <stdlib.h>
#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; i<size; i++)
{
m_Votes[i] = 0;
}
m_Votes.resize(m_Items, 0);
} else {
for (unsigned int i=0; i<m_Items; i++)
{
m_Votes[i] = 0;
}
}
m_pCurMenu = menu;
m_pHandler->OnMenuStart(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_Items; i++)
{
if (m_Votes[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();
}

View File

@ -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<unsigned int> 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<VoteMenuHandler *> m_VoteHandlers;
CVector<IMenuStyle *> m_Styles;
HandleType_t m_StyleType;
HandleType_t m_MenuType;

View File

@ -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; i<numClients; i++)
{
VoteDisplay(clients[i], maxTime);
}
m_pVoteHandler->StartVoting();
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;
}

View File

@ -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;
};

View File

@ -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()

View File

@ -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;

View File

@ -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);
}

View File

@ -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();

326
core/MenuVoting.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include <string.h>
#include <stdlib.h>
#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; i<num_clients; i++)
{
menu->Display(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; i<size; i++)
{
m_Votes[i] = 0;
}
m_Votes.resize(m_Items, 0);
} else {
for (unsigned int i=0; i<m_Items; i++)
{
m_Votes[i] = 0;
}
}
m_pCurMenu = menu;
m_VoteTime = time;
m_VoteFlags = flags;
m_pHandler = handler;
m_pHandler->OnMenuStart(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<m_Items; i++)
{
if (m_Votes[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;
}

97
core/MenuVoting.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_MENUVOTING_H_
#define _INCLUDE_SOURCEMOD_MENUVOTING_H_
#include <IMenuManager.h>
#include <IPlayerHelpers.h>
#include <sh_vector.h>
#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<unsigned int> 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_

View File

@ -336,6 +336,10 @@
RelativePath="..\MenuStyle_Valve.cpp"
>
</File>
<File
RelativePath="..\MenuVoting.cpp"
>
</File>
<File
RelativePath="..\PlayerManager.cpp"
>
@ -474,6 +478,10 @@
RelativePath="..\MenuStyle_Valve.h"
>
</File>
<File
RelativePath="..\MenuVoting.h"
>
</File>
<File
RelativePath="..\PlayerManager.h"
>

View File

@ -32,6 +32,7 @@
#include "sm_globals.h"
#include <sh_stack.h>
#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; i<results->num_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; i<results->num_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; i<results->num_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 *)&params[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},
};

View File

@ -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;

View File

@ -45,7 +45,6 @@ struct TracedCall
unsigned int chain;
};
class CContextTrace : public IContextTrace
{
public:

View File

@ -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.

View File

@ -37,7 +37,7 @@
#include <IHandleSys.h>
#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.
@ -532,46 +537,6 @@ namespace SourceMod
*/
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;
};
}