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