added experimental voting API

--HG--
extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40883
This commit is contained in:
David Anderson 2007-06-05 16:27:45 +00:00
parent 2884483bef
commit aea69f5fe8
9 changed files with 352 additions and 285 deletions

View File

@ -12,9 +12,9 @@
* Version: $Id$
*/
#include <stdarg.h>
#include <time.h>
#include <stdarg.h>
#include <stdlib.h>
#include "MenuManager.h"
#include "sm_stringutil.h"
#include "sourcemm_api.h"
@ -25,115 +25,188 @@
MenuManager g_Menus;
/*************************************
*************************************
**** BROADCAST HANDLING WRAPPERS ****
*************************************
*************************************/
/*******************************
*******************************
******** VOTE HANDLER *********
*******************************
*******************************/
BroadcastHandler::BroadcastHandler(IMenuHandler *handler) : m_pHandler(handler), numClients(0)
{
}
unsigned int BroadcastHandler::GetMenuAPIVersion2()
unsigned int VoteMenuHandler::GetMenuAPIVersion2()
{
return m_pHandler->GetMenuAPIVersion2();
}
void BroadcastHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
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()
{
unsigned int chosen = 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() % static_cast<unsigned int>(m_Votes.size());
goto picked_item;
}
/* We can't have more dups than this!
* This is the max number of players.
*/
unsigned int dup_array[256];
unsigned int dup_count = 0;
size_t highest = 0;
for (size_t i=1; i<m_Votes.size(); 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;
}
}
/* 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++] = (unsigned int)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 = (unsigned int)highest;
}
picked_item:
m_pHandler->OnMenuVoteEnd(m_pCurMenu, chosen);
m_pHandler->OnMenuEnd(m_pCurMenu);
InternalReset();
}
void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
{
m_Clients++;
}
void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu)
{
DecrementPlayerCount();
}
void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
{
m_pHandler->OnMenuCancel(menu, client, reason);
}
void BroadcastHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
{
numClients++;
m_pHandler->OnMenuDisplay(menu, client, display);
}
void BroadcastHandler::OnBroadcastEnd(IBaseMenu *menu)
void VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display)
{
g_Menus.FreeBroadcastHandler(this);
m_pHandler->OnMenuDisplayItem(menu, client, item, display);
}
void BroadcastHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
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 BroadcastHandler::OnMenuEnd(IBaseMenu *menu)
void VoteMenuHandler::Reset(IMenuHandler *mh)
{
assert(numClients > 0);
/* Only fire if all clients have gotten a menu end */
if (--numClients == 0)
{
IMenuHandler *pHandler = m_pHandler;
OnBroadcastEnd(menu);
pHandler->OnMenuEnd(menu);
}
m_pHandler = mh;
InternalReset();
}
VoteHandler::VoteHandler(IMenuVoteHandler *handler)
: BroadcastHandler(handler), m_pVoteHandler(handler)
void VoteMenuHandler::InternalReset()
{
}
void VoteHandler::Initialize(IBaseMenu *menu)
{
unsigned int numItems = menu->GetItemCount();
if (m_counts.size() >= numItems)
{
for (size_t i=0; i<numItems; i++)
{
m_counts[i] = 0;
}
} else {
for (size_t i=0; i<m_counts.size(); i++)
{
m_counts[i] = 0;
}
m_counts.resize(numItems, 0);
}
}
void VoteHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
{
if (item < numItems)
{
m_counts[item]++;
}
BroadcastHandler::OnMenuSelect(menu, client, item);
}
void VoteHandler::OnBroadcastEnd(IBaseMenu *menu)
{
m_ties.clear();
size_t highest = 0;
for (size_t i=1; i<numItems; i++)
{
if (m_counts[i] > m_counts[highest])
{
m_ties.clear();
highest = i;
} else if (m_counts[i] == m_counts[highest]) {
m_ties.push_back(i);
}
}
if (m_ties.size())
{
m_ties.push_back(highest);
srand(static_cast<unsigned int>(time(NULL)));
highest = m_ties[rand() % m_ties.size()];
}
m_pVoteHandler->OnMenuVoteEnd(menu, highest);
g_Menus.FreeVoteHandler(this);
m_Clients = 0;
m_Items = 0;
m_bStarted = false;
m_pCurMenu = NULL;
m_NumVotes = 0;
}
/*******************************
@ -169,12 +242,6 @@ void MenuManager::OnSourceModAllShutdown()
g_HandleSys.RemoveType(m_MenuType, g_pCoreIdent);
g_HandleSys.RemoveType(m_StyleType, g_pCoreIdent);
while (!m_BroadcastHandlers.empty())
{
delete m_BroadcastHandlers.front();
m_BroadcastHandlers.pop();
}
while (!m_VoteHandlers.empty())
{
delete m_VoteHandlers.front();
@ -659,119 +726,34 @@ skip_search:
return display;
}
#if 0
unsigned int MenuManager::BroadcastMenu(IBaseMenu *menu,
IMenuHandler *handler,
int clients[],
unsigned int numClients,
unsigned int time)
{
BroadcastHandler *bh;
if (m_BroadcastHandlers.empty())
{
bh = new BroadcastHandler(handler);
} else {
bh = m_BroadcastHandlers.front();
m_BroadcastHandlers.pop();
bh->m_pHandler = handler;
bh->numClients = 0;
}
handler->OnMenuStart(menu);
unsigned int total = 0;
for (unsigned int i=0; i<numClients; i++)
{
/* Only continue if displaying works */
if (!menu->Display(clients[i], bh, time))
{
continue;
}
/* :TODO: Allow sourcetv only, not all bots */
CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
if (player->IsFakeClient())
{
continue;
}
total++;
}
if (!total)
{
/* End the broadcast here */
handler->OnMenuEnd(menu);
FreeBroadcastHandler(bh);
}
return total;
}
unsigned int MenuManager::VoteMenu(IBaseMenu *menu,
IMenuVoteHandler *handler,
int clients[],
unsigned int numClients,
unsigned int time)
{
VoteHandler *vh;
if (m_VoteHandlers.empty())
{
vh = new VoteHandler(handler);
} else {
vh = m_VoteHandlers.front();
m_VoteHandlers.pop();
vh->m_pHandler = handler;
vh->numClients = 0;
}
vh->Initialize(menu);
handler->OnMenuStart(menu);
unsigned int total = 0;
for (unsigned int i=0; i<numClients; i++)
{
/* Only continue if displaying works */
if (!menu->Display(clients[i], vh, time))
{
continue;
}
/* :TODO: Allow sourcetv only, not all bots */
CPlayer *player = g_Players.GetPlayerByIndex(clients[i]);
if (player->IsFakeClient())
{
continue;
}
total++;
}
if (!total)
{
/* End the broadcast here */
handler->OnMenuEnd(menu);
FreeVoteHandler(vh);
}
return total;
}
#endif
void MenuManager::FreeBroadcastHandler(BroadcastHandler *bh)
{
m_BroadcastHandlers.push(bh);
}
void MenuManager::FreeVoteHandler(VoteHandler *vh)
{
m_VoteHandlers.push(vh);
}
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);
}

View File

@ -25,36 +25,36 @@
using namespace SourceMod;
using namespace SourceHook;
class BroadcastHandler : public IMenuHandler
class VoteMenuHandler : public IVoteMenuHandler
{
public:
BroadcastHandler(IMenuHandler *handler);
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 OnMenuEnd(IBaseMenu *menu);
void OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason);
unsigned int GetMenuAPIVersion2();
void OnMenuEnd(IBaseMenu *menu);
void OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style);
void OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display);
public: //IVoteMenuHandler
bool IsVoteInProgress();
void InitializeVoting(IBaseMenu *menu);
void StartVoting();
public:
virtual void OnBroadcastEnd(IBaseMenu *menu);
public:
IMenuHandler *m_pHandler;
unsigned int numClients;
};
class VoteHandler : public BroadcastHandler
{
public:
VoteHandler(IMenuVoteHandler *handler);
public:
void Initialize(IBaseMenu *menu);
void OnMenuSelect(IBaseMenu *menu, int client, unsigned int item);
void OnBroadcastEnd(IBaseMenu *menu);
void Reset(IMenuHandler *mh);
private:
CVector<unsigned int> m_counts;
CVector<unsigned int> m_ties;
unsigned int numItems;
IMenuVoteHandler *m_pVoteHandler;
void DecrementPlayerCount();
void EndVoting();
void InternalReset();
private:
IMenuHandler *m_pHandler;
unsigned int m_Clients;
unsigned int m_Items;
CVector<unsigned int> m_Votes;
CVector<unsigned int> m_Dups;
IBaseMenu *m_pCurMenu;
bool m_bStarted;
unsigned int m_NumVotes;
};
class MenuManager :
@ -84,35 +84,24 @@ public:
unsigned int GetStyleCount();
IMenuStyle *GetStyle(unsigned int index);
IMenuStyle *FindStyleByName(const char *name);
unsigned int BroadcastMenu(IBaseMenu *menu,
IMenuHandler *handler,
int clients[],
unsigned int numClients,
unsigned int time);
unsigned int VoteMenu(IBaseMenu *menu,
IMenuVoteHandler *handler,
int clients[],
unsigned int numClients,
unsigned int time);
IMenuStyle *GetDefaultStyle();
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);
public: //IHandleTypeDispatch
void OnHandleDestroy(HandleType_t type, void *object);
public:
HandleError ReadMenuHandle(Handle_t handle, IBaseMenu **menu);
HandleError ReadStyleHandle(Handle_t handle, IMenuStyle **style);
protected:
void FreeBroadcastHandler(BroadcastHandler *bh);
void FreeVoteHandler(VoteHandler *vh);
Handle_t CreateMenuHandle(IBaseMenu *menu, IdentityToken_t *pOwner);
Handle_t CreateStyleHandle(IMenuStyle *style);
private:
int m_ShowMenu;
IMenuStyle *m_pDefaultStyle;
CStack<BroadcastHandler *> m_BroadcastHandlers;
CStack<VoteHandler *> m_VoteHandlers;
CStack<VoteMenuHandler *> m_VoteHandlers;
CVector<IMenuStyle *> m_Styles;
HandleType_t m_StyleType;
HandleType_t m_MenuType;

View File

@ -434,12 +434,14 @@ 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_ExitButton(true),
m_bShouldDelete(false), m_bCancelling(false), m_pOwner(pOwner ? pOwner : g_pCoreIdent),
m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler)
m_bDeleting(false), m_bWillFreeHandle(false), m_hHandle(BAD_HANDLE), m_pHandler(pHandler),
m_pVoteHandler(NULL)
{
}
CBaseMenu::~CBaseMenu()
{
g_Menus.ReleaseVoteWrapper(m_pVoteHandler);
}
Handle_t CBaseMenu::GetHandle()
@ -647,3 +649,24 @@ void CBaseMenu::InternalDelete()
delete this;
}
bool CBaseMenu::BroadcastVote(int clients[],
unsigned int numClients,
unsigned int maxTime,
unsigned int flags)
{
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();
}

View File

@ -111,6 +111,12 @@ public:
virtual void Destroy(bool releaseHandle);
virtual void Cancel_Finally() =0;
virtual Handle_t GetHandle();
bool BroadcastVote(int clients[],
unsigned int numClients,
unsigned int maxTime,
unsigned int flags=0);
public:
virtual void VoteDisplay(int client, unsigned int maxTime) =0;
private:
void InternalDelete();
protected:
@ -127,6 +133,7 @@ protected:
bool m_bWillFreeHandle;
Handle_t m_hHandle;
IMenuHandler *m_pHandler;
IVoteMenuHandler *m_pVoteHandler;
};
#endif //_INCLUDE_MENUSTYLE_BASE_H

View File

@ -366,9 +366,24 @@ IMenuPanel *CRadioMenu::CreatePanel()
bool CRadioMenu::Display(int client, unsigned int time)
{
if (m_bCancelling)
{
return false;
}
return g_RadioMenuStyle.DoClientMenu(client, this, m_pHandler, time);
}
void CRadioMenu::VoteDisplay(int client, unsigned int maxTime)
{
if (m_bCancelling)
{
return;
}
g_RadioMenuStyle.DoClientMenu(client, this, m_pVoteHandler, maxTime);
}
void CRadioMenu::Cancel_Finally()
{
g_RadioMenuStyle.CancelMenu(this);

View File

@ -98,6 +98,7 @@ public:
IMenuPanel *CreatePanel();
bool Display(int client, unsigned int time);
void Cancel_Finally();
void VoteDisplay(int client, unsigned int maxTime);
};
extern CRadioStyle g_RadioMenuStyle;

View File

@ -375,6 +375,16 @@ bool CValveMenu::Display(int client, unsigned int time)
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);
}
IMenuPanel *CValveMenu::CreatePanel()
{
return new CValveMenuDisplay(this);

View File

@ -103,6 +103,7 @@ public: //IBaseMenu
bool SetExitButton(bool set);
bool SetPagination(unsigned int itemsPerPage);
bool Display(int client, unsigned int time);
void VoteDisplay(int client, unsigned int maxTime);
public: //CBaseMenu
void Cancel_Finally();
private:

View File

@ -23,7 +23,7 @@
#include <IHandleSys.h>
#define SMINTERFACE_MENUMANAGER_NAME "IMenuManager"
#define SMINTERFACE_MENUMANAGER_VERSION 1
#define SMINTERFACE_MENUMANAGER_VERSION 2
/**
* @file IMenuManager.h
@ -498,6 +498,23 @@ 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;
};
/**
@ -600,21 +617,55 @@ namespace SourceMod
virtual void OnMenuDisplayItem(IBaseMenu *menu, int client, unsigned int item, const char **display)
{
}
};
/**
* @brief Handles a vote menu.
*/
class IMenuVoteHandler : public IMenuHandler
{
public:
/**
* @brief Called when a vote ends.
* @brief Called when a vote has been started and displayed to
* clients. This is called after OnMenuStart() and OnMenuDisplay(),
* but before OnMenuSelect().
*
* @param menu Menu pointer.
*/
virtual void OnMenuVoteStart(IBaseMenu *menu)
{
}
/**
* @brief Called when a vote ends. This is automatically called by the
* wrapper, and never needs to called from a style implementation.
*
* @param menu Menu pointer.
* @param item Item position that was chosen by a majority.
*/
virtual void OnMenuVoteEnd(IBaseMenu *menu, unsigned int item) =0;
virtual void OnMenuVoteEnd(IBaseMenu *menu, unsigned int item)
{
}
};
/**
* @brief Contains functions for managing a vote handler.
*/
class IVoteMenuHandler : public IMenuHandler
{
public:
/**
* @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 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;
};
/**
@ -640,39 +691,6 @@ namespace SourceMod
*/
virtual IMenuStyle *FindStyleByName(const char *name) =0;
#if 0
/**
* @brief Broadcasts a menu to a number of clients.
*
* @param menu Menu pointer.
* @param clients Array of client indexes.
* @param numClients Number of clients in the array.
* @param time Time to hold the menu.
* @return Number of clients the menu will be waiting on.
*/
virtual unsigned int BroadcastMenu(IBaseMenu *menu,
IMenuHandler *handler,
int clients[],
unsigned int numClients,
unsigned int time) =0;
/**
* @brief Broadcasts a menu to a number of clients as a vote menu.
*
* @param menu Menu pointer.
* @param handler IMenuHandler pointer.
* @param clients Array of client indexes.
* @param numClients Number of clients in the array.
* @param time Time to hold the menu.
* @return Number of clients the menu will be waiting on.
*/
virtual unsigned int VoteMenu(IBaseMenu *menu,
IMenuVoteHandler *handler,
int clients[],
unsigned int numClients,
unsigned int time) =0;
#endif
/**
* @brief Returns the default draw style Core is using.
*
@ -695,6 +713,27 @@ namespace SourceMod
* be freed using IMenuPanel::DeleteThis.
*/
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.
*
* @param mh Menu handler to wrap around.
* @return An IMenuHandler pointer that is a wrapper
* around IMenuHandler callbacks to invoke
* voting related callbacks.
*/
virtual IVoteMenuHandler *CreateVoteWrapper(IMenuHandler *mh) =0;
/**
* @brief Frees a standard voting wrapper.
*
* @param mh Menu handler pointer created by
* CreateVoteWrapper(). NULL values will be
* safely ignored.
*/
virtual void ReleaseVoteWrapper(IVoteMenuHandler *mh) =0;
};
}