diff -r 000000000000 -r c24a6fa957db core/MenuVoting.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/MenuVoting.cpp Sun Jul 06 04:49:55 2008 +0000
@@ -0,0 +1,491 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod
+ * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#include
+#include
+#include "MenuVoting.h"
+#include "PlayerManager.h"
+#include "sourcemm_api.h"
+
+float g_next_vote = 0.0f;
+
+#if defined ORANGEBOX_BUILD
+void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue);
+#else
+void OnVoteDelayChange(ConVar *cvar, const char *value);
+#endif
+ConVar sm_vote_delay("sm_vote_delay",
+ "30",
+ 0,
+ "Sets the recommended time in between public votes",
+ false,
+ 0.0,
+ false,
+ 0.0,
+ OnVoteDelayChange);
+
+#if defined ORANGEBOX_BUILD
+void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue)
+#else
+void OnVoteDelayChange(ConVar *cvar, const char *value)
+#endif
+{
+ /* See if the new vote delay isn't something we need to account for */
+ if (sm_vote_delay.GetFloat() < 1.0f)
+ {
+ g_next_vote = 0.0f;
+ return;
+ }
+
+ /* If there was never a last vote, ignore this change */
+ if (g_next_vote < 0.1f)
+ {
+ return;
+ }
+
+ /* Subtract the original value, then add the new one. */
+ g_next_vote -= (float)atof(value);
+ g_next_vote += sm_vote_delay.GetFloat();
+}
+
+unsigned int VoteMenuHandler::GetRemainingVoteDelay()
+{
+ if (g_next_vote <= gpGlobals->curtime)
+ {
+ return 0;
+ }
+
+ return (unsigned int)(g_next_vote - gpGlobals->curtime);
+}
+
+void VoteMenuHandler::OnSourceModAllInitialized()
+{
+ g_Players.AddClientListener(this);
+}
+
+void VoteMenuHandler::OnSourceModShutdown()
+{
+ g_Players.RemoveClientListener(this);
+}
+
+void VoteMenuHandler::OnSourceModLevelChange(const char *mapName)
+{
+ g_next_vote = 0.0f;
+}
+
+unsigned int VoteMenuHandler::GetMenuAPIVersion2()
+{
+ return m_pHandler->GetMenuAPIVersion2();
+}
+
+void VoteMenuHandler::OnClientDisconnected(int client)
+{
+ if (!IsVoteInProgress())
+ {
+ return;
+ }
+
+ /* Wipe out their vote if they had one. We have to make sure the the the
+ * newly connected client is not allowed to vote.
+ */
+ int item;
+ if ((item = m_ClientVotes[client]) >= -1)
+ {
+ if (item >= 0)
+ {
+ assert((unsigned)item < m_Items);
+ assert(m_Votes[item] > 0);
+ m_Votes[item]--;
+ }
+ m_ClientVotes[client] = -2;
+ }
+}
+
+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;
+ }
+
+ /* Note: we can use game time and not universal time because
+ * if we're voting then players are in-game.
+ */
+
+ float fVoteDelay = sm_vote_delay.GetFloat();
+ if (fVoteDelay < 1.0)
+ {
+ g_next_vote = 0.0;
+ }
+ else
+ {
+ /* This little trick breaks for infinite votes!
+ * However, we just ignore that since those 1) shouldn't exist and
+ * 2) people must be checking IsVoteInProgress() beforehand anyway.
+ */
+ g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time;
+ }
+
+ m_fStartTime = gpGlobals->curtime;
+ m_nMenuTime = max_time;
+
+ for (unsigned int i=0; i 256)
+ {
+ continue;
+ }
+ menu->Display(clients[i], max_time, this);
+ }
+
+ StartVoting();
+
+ return true;
+}
+
+bool VoteMenuHandler::IsClientInVotePool(int client)
+{
+ if (client < 1
+ || client > g_Players.MaxClients()
+ || m_pCurMenu == NULL)
+ {
+ return false;
+ }
+
+ return (m_ClientVotes[client] > -2);
+}
+
+bool VoteMenuHandler::GetClientVoteChoice(int client, unsigned int *pItem)
+{
+ if (!IsClientInVotePool(client)
+ || m_ClientVotes[client] == -1)
+ {
+ return false;
+ }
+
+ *pItem = m_ClientVotes[client];
+
+ return true;
+}
+
+bool VoteMenuHandler::RedrawToClient(int client)
+{
+ unsigned int time_limit;
+
+ if (!IsClientInVotePool(client))
+ {
+ return false;
+ }
+
+ if (m_nMenuTime == MENU_TIME_FOREVER)
+ {
+ time_limit = m_nMenuTime;
+ }
+ else
+ {
+ time_limit = (int)((float)m_nMenuTime - (gpGlobals->curtime - m_fStartTime));
+
+ /* Make sure this doesn't round to zero */
+ if (time_limit == MENU_TIME_FOREVER)
+ {
+ time_limit = 1;
+ }
+ }
+
+ return m_pCurMenu->Display(client, time_limit, this);
+}
+
+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()
+{
+ /* Set when the next delay ends. We ignore cancellation because a menu
+ * was, at one point, displayed, which is all that counts. However, we
+ * do re-calculate the time just in case the menu had no time limit.
+ */
+ float fVoteDelay = sm_vote_delay.GetFloat();
+ if (fVoteDelay < 1.0)
+ {
+ g_next_vote = 0.0;
+ }
+ else
+ {
+ g_next_vote = gpGlobals->curtime + fVoteDelay;
+ }
+
+ 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, VoteCancel_Generic);
+ 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, VoteCancel_NoVotes);
+ handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
+ 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::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
+{
+ if (m_pHandler->GetMenuAPIVersion2() >= 13)
+ {
+ m_pHandler->OnMenuSelect2(menu, client, item, item_on_page);
+ }
+}
+
+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;
+}