core/MenuVoting.cpp
author David Anderson <dvander@alliedmods.net>
Thu Aug 09 19:05:34 2007 +0000 (2007-08-09)
changeset 1291 c6d26de53926
parent 1269 27397584df9c
child 1296 0197a1212cb8
permissions -rw-r--r--
added amb764, vote delay setting
     1 /**
     2  * vim: set ts=4 :
     3  * ================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     6  * ================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or 
     9  * modify it under the terms of the GNU General Public License, 
    10  * version 3.0, as published by the Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful,
    13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15  * GNU General Public License for more details.
    16  *
    17  * You should have received a copy of the GNU General Public License
    18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to 
    21  * link the code of this program (as well as its derivative works) to 
    22  * "Half-Life 2," the "Source Engine," the "SourcePawn JIT," and any 
    23  * Game MODs that run on software by the Valve Corporation.  You must 
    24  * obey the GNU General Public License in all respects for all other 
    25  * code used. Additionally, AlliedModders LLC grants this exception 
    26  * to all derivative works. AlliedModders LLC defines further 
    27  * exceptions, found in LICENSE.txt (as of this writing, version 
    28  * JULY-31-2007), or <http://www.sourcemod.net/license.php>.
    29  *
    30  * Version: $Id$
    31  */
    32 
    33 #include <string.h>
    34 #include <stdlib.h>
    35 #include "MenuVoting.h"
    36 #include "PlayerManager.h"
    37 #include "sourcemm_api.h"
    38 
    39 float g_next_vote = 0.0f;
    40 
    41 void OnVoteDelayChange(ConVar *cvar, const char *value);
    42 ConVar sm_vote_delay("sm_vote_delay",
    43 					 "30",
    44 					 0,
    45 					 "Sets the recommended time in between public votes",
    46 					 false,
    47 					 0.0,
    48 					 false,
    49 					 0.0,
    50 					 OnVoteDelayChange);
    51 
    52 void OnVoteDelayChange(ConVar *cvar, const char *value)
    53 {
    54 	/* See if the new vote delay isn't something we need to account for */
    55 	if (sm_vote_delay.GetFloat() < 1.0f)
    56 	{
    57 		g_next_vote = 0.0f;
    58 		return;
    59 	}
    60 
    61 	/* If there was never a last vote, ignore this change */
    62 	if (g_next_vote < 0.1f)
    63 	{
    64 		return;
    65 	}
    66 
    67 	/* Subtract the original value, then add the new one. */
    68 	g_next_vote -= (float)atof(value);
    69 	g_next_vote += sm_vote_delay.GetFloat();
    70 }
    71 
    72 unsigned int VoteMenuHandler::GetRemainingVoteDelay()
    73 {
    74 	if (g_next_vote <= gpGlobals->curtime)
    75 	{
    76 		return 0;
    77 	}
    78 
    79 	return (unsigned int)(g_next_vote - gpGlobals->curtime);
    80 }
    81 
    82 void VoteMenuHandler::OnSourceModAllInitialized()
    83 {
    84 	g_Players.AddClientListener(this);
    85 }
    86 
    87 void VoteMenuHandler::OnSourceModShutdown()
    88 {
    89 	g_Players.RemoveClientListener(this);
    90 }
    91 
    92 void VoteMenuHandler::OnSourceModLevelChange(const char *mapName)
    93 {
    94 	g_next_vote = 0.0f;
    95 }
    96 
    97 unsigned int VoteMenuHandler::GetMenuAPIVersion2()
    98 {
    99 	return m_pHandler->GetMenuAPIVersion2();
   100 }
   101 
   102 void VoteMenuHandler::OnClientDisconnected(int client)
   103 {
   104 	if (!IsVoteInProgress())
   105 	{
   106 		return;
   107 	}
   108 
   109 	/* Wipe out their vote if they had one */
   110 	int item;
   111 	if ((item = m_ClientVotes[client]) >= 0)
   112 	{
   113 		assert((unsigned)item < m_Items);
   114 		assert(m_Votes[item] > 0);
   115 		m_Votes[item]--;
   116 		m_ClientVotes[client] = -1;
   117 	}
   118 }
   119 
   120 bool VoteMenuHandler::IsVoteInProgress()
   121 {
   122 	return (m_pCurMenu != NULL);
   123 }
   124 
   125 bool VoteMenuHandler::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags/* =0 */)
   126 {
   127 	if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags))
   128 	{
   129 		return false;
   130 	}
   131 
   132 	float fVoteDelay = sm_vote_delay.GetFloat();
   133 	if (fVoteDelay < 1.0)
   134 	{
   135 		g_next_vote = 0.0;
   136 	} else {
   137 		/* This little trick breaks for infinite votes!
   138 		 * However, we just ignore that since those 1) shouldn't exist and 
   139 		 * 2) people must be checking IsVoteInProgress() beforehand anyway.
   140 		 */
   141 		g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time;
   142 	}
   143 
   144 	for (unsigned int i=0; i<num_clients; i++)
   145 	{
   146 		menu->Display(clients[i], max_time, this);
   147 	}
   148 
   149 	StartVoting();
   150 
   151 	return true;
   152 }
   153 
   154 bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, 
   155 									   IMenuHandler *handler,
   156 									   unsigned int time,
   157 									   unsigned int flags)
   158 {
   159 	if (IsVoteInProgress())
   160 	{
   161 		return false;
   162 	}
   163 
   164 	InternalReset();
   165 
   166 	/* Mark all clients as not voting */
   167 	for (int i=1; i<=gpGlobals->maxClients; i++)
   168 	{
   169 		m_ClientVotes[i] = -2;
   170 	}
   171 
   172 	m_Items = menu->GetItemCount();
   173 
   174 	if (m_Votes.size() < (size_t)m_Items)
   175 	{
   176 		/* Only clear the items we need to... */
   177 		size_t size = m_Votes.size();
   178 		for (size_t i=0; i<size; i++)
   179 		{
   180 			m_Votes[i] = 0;
   181 		}
   182 		m_Votes.resize(m_Items, 0);
   183 	} else {
   184 		for (unsigned int i=0; i<m_Items; i++)
   185 		{
   186 			m_Votes[i] = 0;
   187 		}
   188 	}
   189 
   190 	m_pCurMenu = menu;
   191 	m_VoteTime = time;
   192 	m_VoteFlags = flags;
   193 	m_pHandler = handler;
   194 
   195 	m_pHandler->OnMenuStart(m_pCurMenu);
   196 
   197 	return true;
   198 }
   199 
   200 void VoteMenuHandler::StartVoting()
   201 {
   202 	if (!m_pCurMenu)
   203 	{
   204 		return;
   205 	}
   206 
   207 	m_bStarted = true;
   208 
   209 	m_pHandler->OnMenuVoteStart(m_pCurMenu);
   210 
   211 	/* By now we know how many clients were set.  
   212 	 * If there are none, we should end IMMEDIATELY.
   213 	 */
   214 	if (m_Clients == 0)
   215 	{
   216 		EndVoting();
   217 	}
   218 }
   219 
   220 void VoteMenuHandler::DecrementPlayerCount()
   221 {
   222 	assert(m_Clients > 0);
   223 
   224 	m_Clients--;
   225 
   226 	if (m_bStarted && m_Clients == 0)
   227 	{
   228 		EndVoting();
   229 	}
   230 }
   231 
   232 int SortVoteItems(const void *item1, const void *item2)
   233 {
   234 	return ((menu_vote_result_t::menu_item_vote_t *)item2)->count 
   235 		- ((menu_vote_result_t::menu_item_vote_t *)item1)->count;
   236 }
   237 
   238 void VoteMenuHandler::EndVoting()
   239 {
   240 	/* Set when the next delay ends.  We ignore cancellation because a menu
   241 	 * was, at one point, displayed, which is all that counts.  However, we
   242 	 * do re-calculate the time just in case the menu had no time limit.
   243 	 */
   244 	float fVoteDelay = sm_vote_delay.GetFloat();
   245 	if (fVoteDelay < 1.0)
   246 	{
   247 		g_next_vote = 0.0;
   248 	} else {
   249 		g_next_vote = gpGlobals->curtime + fVoteDelay;
   250 	}
   251 
   252 	if (m_bCancelled)
   253 	{
   254 		/* If we were cancelled, don't bother tabulating anything.
   255 		 * Reset just in case someone tries to redraw, which means
   256 		 * we need to save our states.
   257 		 */
   258 		IBaseMenu *menu = m_pCurMenu;
   259 		IMenuHandler *handler = m_pHandler;
   260 		InternalReset();
   261 		handler->OnMenuVoteCancel(menu);
   262 		handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   263 		return;
   264 	}
   265 
   266 	menu_vote_result_t vote;
   267 	menu_vote_result_t::menu_client_vote_t client_vote[256];
   268 	menu_vote_result_t::menu_item_vote_t item_vote[256];
   269 
   270 	memset(&vote, 0, sizeof(vote));
   271 
   272 	/* Build the item list */
   273 	for (unsigned int i=0; i<m_Items; i++)
   274 	{
   275 		if (m_Votes[i] > 0)
   276 		{
   277 			item_vote[vote.num_items].count = m_Votes[i];
   278 			item_vote[vote.num_items].item = i;
   279 			vote.num_votes += m_Votes[i];
   280 			vote.num_items++;
   281 		}
   282 	}
   283 	vote.item_list = item_vote;
   284 
   285 	if (!vote.num_votes)
   286 	{
   287 		IBaseMenu *menu = m_pCurMenu;
   288 		IMenuHandler *handler = m_pHandler;
   289 		InternalReset();
   290 		handler->OnMenuVoteCancel(menu);
   291 		handler->OnMenuEnd(menu, MenuEnd_NoVotes);
   292 		return;
   293 	}
   294 
   295 	/* Build the client list */
   296 	for (int i=1; i<=gpGlobals->maxClients; i++)
   297 	{
   298 		if (m_ClientVotes[i] >= -1)
   299 		{
   300 			client_vote[vote.num_clients].client = i;
   301 			client_vote[vote.num_clients].item = m_ClientVotes[i];
   302 			vote.num_clients++;
   303 		}
   304 	}
   305 	vote.client_list = client_vote;
   306 
   307 	/* Sort the item list descending like we promised */
   308 	qsort(item_vote,
   309 		vote.num_items,
   310 		sizeof(menu_vote_result_t::menu_item_vote_t),
   311 		SortVoteItems);
   312 
   313 	/* Save states, then clear what we've saved.
   314 	 * This makes us re-entrant, which is always the safe way to go.
   315 	 */
   316 	IBaseMenu *menu = m_pCurMenu;
   317 	IMenuHandler *handler = m_pHandler;
   318 	InternalReset();
   319 
   320 	/* Send vote info */
   321 	handler->OnMenuVoteResults(menu, &vote);
   322 	handler->OnMenuEnd(menu, MenuEnd_VotingDone);
   323 }
   324 
   325 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
   326 {
   327 	m_Clients++;
   328 }
   329 
   330 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
   331 {
   332 	DecrementPlayerCount();
   333 }
   334 
   335 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
   336 {
   337 	m_pHandler->OnMenuCancel(menu, client, reason);
   338 }
   339 
   340 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
   341 {
   342 	m_ClientVotes[client] = -1;
   343 	m_pHandler->OnMenuDisplay(menu, client, display);
   344 }
   345 
   346 unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
   347 {
   348 	return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
   349 }
   350 
   351 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
   352 {
   353 	m_pHandler->OnMenuDrawItem(menu, client, item, style);
   354 }
   355 
   356 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
   357 {
   358 	/* Check by our item count, NOT the vote array size */
   359 	if (item < m_Items)
   360 	{
   361 		m_ClientVotes[client] = item;
   362 		m_Votes[item]++;
   363 		m_NumVotes++;
   364 	}
   365 
   366 	m_pHandler->OnMenuSelect(menu, client, item);
   367 }
   368 
   369 void VoteMenuHandler::InternalReset()
   370 {
   371 	m_Clients = 0;
   372 	m_Items = 0;
   373 	m_bStarted = false;
   374 	m_pCurMenu = NULL;
   375 	m_NumVotes = 0;
   376 	m_bCancelled = false;
   377 	m_pHandler = NULL;
   378 }
   379 
   380 void VoteMenuHandler::CancelVoting()
   381 {
   382 	if (m_bCancelled || !m_pCurMenu)
   383 	{
   384 		return;
   385 	}
   386 	m_bCancelled = true;
   387 	m_pCurMenu->Cancel();
   388 }
   389 
   390 IBaseMenu *VoteMenuHandler::GetCurrentMenu()
   391 {
   392 	return m_pCurMenu;
   393 }
   394 
   395 bool VoteMenuHandler::IsCancelling()
   396 {
   397 	return m_bCancelled;
   398 }