core/MenuVoting.cpp
author David Anderson <dvander@alliedmods.net>
Sun Nov 16 16:06:18 2008 -0800 (2008-11-16)
changeset 2411 8dfaa3d730b5
parent 2289 44045ead61cd
child 2400 76b7b4575e21
permissions -rw-r--r--
Removed pointless "systems" folder from core.
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 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 defined ORANGEBOX_BUILD
    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 defined ORANGEBOX_BUILD
    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)
   209 {
   210 	unsigned int time_limit;
   211 
   212 	if (!IsClientInVotePool(client))
   213 	{
   214 		return false;
   215 	}
   216 
   217 	if (m_nMenuTime == MENU_TIME_FOREVER)
   218 	{
   219 		time_limit = m_nMenuTime;
   220 	}
   221 	else
   222 	{
   223 		time_limit = (int)((float)m_nMenuTime - (gpGlobals->curtime - m_fStartTime));
   224 		
   225 		/* Make sure this doesn't round to zero */
   226 		if (time_limit == MENU_TIME_FOREVER)
   227 		{
   228 			time_limit = 1;
   229 		}
   230 	}
   231 
   232 	return m_pCurMenu->Display(client, time_limit, this);
   233 }
   234 
   235 bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, 
   236 									   IMenuHandler *handler,
   237 									   unsigned int time,
   238 									   unsigned int flags)
   239 {
   240 	if (IsVoteInProgress())
   241 	{
   242 		return false;
   243 	}
   244 
   245 	InternalReset();
   246 
   247 	/* Mark all clients as not voting */
   248 	for (int i=1; i<=gpGlobals->maxClients; i++)
   249 	{
   250 		m_ClientVotes[i] = -2;
   251 	}
   252 
   253 	m_Items = menu->GetItemCount();
   254 
   255 	if (m_Votes.size() < (size_t)m_Items)
   256 	{
   257 		/* Only clear the items we need to... */
   258 		size_t size = m_Votes.size();
   259 		for (size_t i=0; i<size; i++)
   260 		{
   261 			m_Votes[i] = 0;
   262 		}
   263 		m_Votes.resize(m_Items, 0);
   264 	}
   265 	else
   266 	{
   267 		for (unsigned int i=0; i<m_Items; i++)
   268 		{
   269 			m_Votes[i] = 0;
   270 		}
   271 	}
   272 
   273 	m_pCurMenu = menu;
   274 	m_VoteTime = time;
   275 	m_VoteFlags = flags;
   276 	m_pHandler = handler;
   277 
   278 	m_pHandler->OnMenuStart(m_pCurMenu);
   279 
   280 	return true;
   281 }
   282 
   283 void VoteMenuHandler::StartVoting()
   284 {
   285 	if (!m_pCurMenu)
   286 	{
   287 		return;
   288 	}
   289 
   290 	m_bStarted = true;
   291 
   292 	m_pHandler->OnMenuVoteStart(m_pCurMenu);
   293 
   294 	/* By now we know how many clients were set.  
   295 	 * If there are none, we should end IMMEDIATELY.
   296 	 */
   297 	if (m_Clients == 0)
   298 	{
   299 		EndVoting();
   300 	}
   301 }
   302 
   303 void VoteMenuHandler::DecrementPlayerCount()
   304 {
   305 	assert(m_Clients > 0);
   306 
   307 	m_Clients--;
   308 
   309 	if (m_bStarted && m_Clients == 0)
   310 	{
   311 		EndVoting();
   312 	}
   313 }
   314 
   315 int SortVoteItems(const void *item1, const void *item2)
   316 {
   317 	return ((menu_vote_result_t::menu_item_vote_t *)item2)->count 
   318 		- ((menu_vote_result_t::menu_item_vote_t *)item1)->count;
   319 }
   320 
   321 void VoteMenuHandler::EndVoting()
   322 {
   323 	/* Set when the next delay ends.  We ignore cancellation because a menu
   324 	 * was, at one point, displayed, which is all that counts.  However, we
   325 	 * do re-calculate the time just in case the menu had no time limit.
   326 	 */
   327 	float fVoteDelay = sm_vote_delay.GetFloat();
   328 	if (fVoteDelay < 1.0)
   329 	{
   330 		g_next_vote = 0.0;
   331 	}
   332 	else
   333 	{
   334 		g_next_vote = gpGlobals->curtime + fVoteDelay;
   335 	}
   336 
   337 	if (m_bCancelled)
   338 	{
   339 		/* If we were cancelled, don't bother tabulating anything.
   340 		 * Reset just in case someone tries to redraw, which means
   341 		 * we need to save our states.
   342 		 */
   343 		IBaseMenu *menu = m_pCurMenu;
   344 		IMenuHandler *handler = m_pHandler;
   345 		InternalReset();
   346 		handler->OnMenuVoteCancel(menu, VoteCancel_Generic);
   347 		handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   348 		return;
   349 	}
   350 
   351 	menu_vote_result_t vote;
   352 	menu_vote_result_t::menu_client_vote_t client_vote[256];
   353 	menu_vote_result_t::menu_item_vote_t item_vote[256];
   354 
   355 	memset(&vote, 0, sizeof(vote));
   356 
   357 	/* Build the item list */
   358 	for (unsigned int i=0; i<m_Items; i++)
   359 	{
   360 		if (m_Votes[i] > 0)
   361 		{
   362 			item_vote[vote.num_items].count = m_Votes[i];
   363 			item_vote[vote.num_items].item = i;
   364 			vote.num_votes += m_Votes[i];
   365 			vote.num_items++;
   366 		}
   367 	}
   368 	vote.item_list = item_vote;
   369 
   370 	if (!vote.num_votes)
   371 	{
   372 		IBaseMenu *menu = m_pCurMenu;
   373 		IMenuHandler *handler = m_pHandler;
   374 		InternalReset();
   375 		handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes);
   376 		handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   377 		return;
   378 	}
   379 
   380 	/* Build the client list */
   381 	for (int i=1; i<=gpGlobals->maxClients; i++)
   382 	{
   383 		if (m_ClientVotes[i] >= -1)
   384 		{
   385 			client_vote[vote.num_clients].client = i;
   386 			client_vote[vote.num_clients].item = m_ClientVotes[i];
   387 			vote.num_clients++;
   388 		}
   389 	}
   390 	vote.client_list = client_vote;
   391 
   392 	/* Sort the item list descending like we promised */
   393 	qsort(item_vote,
   394 		vote.num_items,
   395 		sizeof(menu_vote_result_t::menu_item_vote_t),
   396 		SortVoteItems);
   397 
   398 	/* Save states, then clear what we've saved.
   399 	 * This makes us re-entrant, which is always the safe way to go.
   400 	 */
   401 	IBaseMenu *menu = m_pCurMenu;
   402 	IMenuHandler *handler = m_pHandler;
   403 	InternalReset();
   404 
   405 	/* Send vote info */
   406 	handler->OnMenuVoteResults(menu, &vote);
   407 	handler->OnMenuEnd(menu, MenuEnd_VotingDone);
   408 }
   409 
   410 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
   411 {
   412 	m_Clients++;
   413 }
   414 
   415 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
   416 {
   417 	DecrementPlayerCount();
   418 }
   419 
   420 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
   421 {
   422 	m_pHandler->OnMenuCancel(menu, client, reason);
   423 }
   424 
   425 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
   426 {
   427 	m_ClientVotes[client] = -1;
   428 	m_pHandler->OnMenuDisplay(menu, client, display);
   429 }
   430 
   431 unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
   432 {
   433 	return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
   434 }
   435 
   436 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
   437 {
   438 	m_pHandler->OnMenuDrawItem(menu, client, item, style);
   439 }
   440 
   441 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
   442 {
   443 	/* Check by our item count, NOT the vote array size */
   444 	if (item < m_Items)
   445 	{
   446 		m_ClientVotes[client] = item;
   447 		m_Votes[item]++;
   448 		m_NumVotes++;
   449 	}
   450 
   451 	m_pHandler->OnMenuSelect(menu, client, item);
   452 }
   453 
   454 void VoteMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
   455 {
   456 	if (m_pHandler->GetMenuAPIVersion2() >= 13)
   457 	{
   458 		m_pHandler->OnMenuSelect2(menu, client, item, item_on_page);
   459 	}
   460 }
   461 
   462 void VoteMenuHandler::InternalReset()
   463 {
   464 	m_Clients = 0;
   465 	m_Items = 0;
   466 	m_bStarted = false;
   467 	m_pCurMenu = NULL;
   468 	m_NumVotes = 0;
   469 	m_bCancelled = false;
   470 	m_pHandler = NULL;
   471 }
   472 
   473 void VoteMenuHandler::CancelVoting()
   474 {
   475 	if (m_bCancelled || !m_pCurMenu)
   476 	{
   477 		return;
   478 	}
   479 	m_bCancelled = true;
   480 	m_pCurMenu->Cancel();
   481 }
   482 
   483 IBaseMenu *VoteMenuHandler::GetCurrentMenu()
   484 {
   485 	return m_pCurMenu;
   486 }
   487 
   488 bool VoteMenuHandler::IsCancelling()
   489 {
   490 	return m_bCancelled;
   491 }