core/MenuVoting.cpp
author Scott Ehlert <ds@alliedmods.net>
Wed Aug 15 06:19:30 2007 +0000 (2007-08-15)
changeset 1330 ab94316bb52b
parent 1296 0197a1212cb8
child 1474 a3b73df84d88
permissions -rw-r--r--
Updated license headers on virtually all files with extremely minor changes in the name of some sort of strange consistency.
All plugin and include file headers also have been changed to say about GPL3 instead of GPL2.

(This day shall henceforth be known as the Eighty Column Massacre of '07)
     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 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 void OnVoteDelayChange(ConVar *cvar, const char *value);
    41 ConVar sm_vote_delay("sm_vote_delay",
    42 					 "30",
    43 					 0,
    44 					 "Sets the recommended time in between public votes",
    45 					 false,
    46 					 0.0,
    47 					 false,
    48 					 0.0,
    49 					 OnVoteDelayChange);
    50 
    51 void OnVoteDelayChange(ConVar *cvar, const char *value)
    52 {
    53 	/* See if the new vote delay isn't something we need to account for */
    54 	if (sm_vote_delay.GetFloat() < 1.0f)
    55 	{
    56 		g_next_vote = 0.0f;
    57 		return;
    58 	}
    59 
    60 	/* If there was never a last vote, ignore this change */
    61 	if (g_next_vote < 0.1f)
    62 	{
    63 		return;
    64 	}
    65 
    66 	/* Subtract the original value, then add the new one. */
    67 	g_next_vote -= (float)atof(value);
    68 	g_next_vote += sm_vote_delay.GetFloat();
    69 }
    70 
    71 unsigned int VoteMenuHandler::GetRemainingVoteDelay()
    72 {
    73 	if (g_next_vote <= gpGlobals->curtime)
    74 	{
    75 		return 0;
    76 	}
    77 
    78 	return (unsigned int)(g_next_vote - gpGlobals->curtime);
    79 }
    80 
    81 void VoteMenuHandler::OnSourceModAllInitialized()
    82 {
    83 	g_Players.AddClientListener(this);
    84 }
    85 
    86 void VoteMenuHandler::OnSourceModShutdown()
    87 {
    88 	g_Players.RemoveClientListener(this);
    89 }
    90 
    91 void VoteMenuHandler::OnSourceModLevelChange(const char *mapName)
    92 {
    93 	g_next_vote = 0.0f;
    94 }
    95 
    96 unsigned int VoteMenuHandler::GetMenuAPIVersion2()
    97 {
    98 	return m_pHandler->GetMenuAPIVersion2();
    99 }
   100 
   101 void VoteMenuHandler::OnClientDisconnected(int client)
   102 {
   103 	if (!IsVoteInProgress())
   104 	{
   105 		return;
   106 	}
   107 
   108 	/* Wipe out their vote if they had one */
   109 	int item;
   110 	if ((item = m_ClientVotes[client]) >= 0)
   111 	{
   112 		assert((unsigned)item < m_Items);
   113 		assert(m_Votes[item] > 0);
   114 		m_Votes[item]--;
   115 		m_ClientVotes[client] = -1;
   116 	}
   117 }
   118 
   119 bool VoteMenuHandler::IsVoteInProgress()
   120 {
   121 	return (m_pCurMenu != NULL);
   122 }
   123 
   124 bool VoteMenuHandler::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags/* =0 */)
   125 {
   126 	if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags))
   127 	{
   128 		return false;
   129 	}
   130 
   131 	float fVoteDelay = sm_vote_delay.GetFloat();
   132 	if (fVoteDelay < 1.0)
   133 	{
   134 		g_next_vote = 0.0;
   135 	} else {
   136 		/* This little trick breaks for infinite votes!
   137 		 * However, we just ignore that since those 1) shouldn't exist and 
   138 		 * 2) people must be checking IsVoteInProgress() beforehand anyway.
   139 		 */
   140 		g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time;
   141 	}
   142 
   143 	for (unsigned int i=0; i<num_clients; i++)
   144 	{
   145 		menu->Display(clients[i], max_time, this);
   146 	}
   147 
   148 	StartVoting();
   149 
   150 	return true;
   151 }
   152 
   153 bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, 
   154 									   IMenuHandler *handler,
   155 									   unsigned int time,
   156 									   unsigned int flags)
   157 {
   158 	if (IsVoteInProgress())
   159 	{
   160 		return false;
   161 	}
   162 
   163 	InternalReset();
   164 
   165 	/* Mark all clients as not voting */
   166 	for (int i=1; i<=gpGlobals->maxClients; i++)
   167 	{
   168 		m_ClientVotes[i] = -2;
   169 	}
   170 
   171 	m_Items = menu->GetItemCount();
   172 
   173 	if (m_Votes.size() < (size_t)m_Items)
   174 	{
   175 		/* Only clear the items we need to... */
   176 		size_t size = m_Votes.size();
   177 		for (size_t i=0; i<size; i++)
   178 		{
   179 			m_Votes[i] = 0;
   180 		}
   181 		m_Votes.resize(m_Items, 0);
   182 	} else {
   183 		for (unsigned int i=0; i<m_Items; i++)
   184 		{
   185 			m_Votes[i] = 0;
   186 		}
   187 	}
   188 
   189 	m_pCurMenu = menu;
   190 	m_VoteTime = time;
   191 	m_VoteFlags = flags;
   192 	m_pHandler = handler;
   193 
   194 	m_pHandler->OnMenuStart(m_pCurMenu);
   195 
   196 	return true;
   197 }
   198 
   199 void VoteMenuHandler::StartVoting()
   200 {
   201 	if (!m_pCurMenu)
   202 	{
   203 		return;
   204 	}
   205 
   206 	m_bStarted = true;
   207 
   208 	m_pHandler->OnMenuVoteStart(m_pCurMenu);
   209 
   210 	/* By now we know how many clients were set.  
   211 	 * If there are none, we should end IMMEDIATELY.
   212 	 */
   213 	if (m_Clients == 0)
   214 	{
   215 		EndVoting();
   216 	}
   217 }
   218 
   219 void VoteMenuHandler::DecrementPlayerCount()
   220 {
   221 	assert(m_Clients > 0);
   222 
   223 	m_Clients--;
   224 
   225 	if (m_bStarted && m_Clients == 0)
   226 	{
   227 		EndVoting();
   228 	}
   229 }
   230 
   231 int SortVoteItems(const void *item1, const void *item2)
   232 {
   233 	return ((menu_vote_result_t::menu_item_vote_t *)item2)->count 
   234 		- ((menu_vote_result_t::menu_item_vote_t *)item1)->count;
   235 }
   236 
   237 void VoteMenuHandler::EndVoting()
   238 {
   239 	/* Set when the next delay ends.  We ignore cancellation because a menu
   240 	 * was, at one point, displayed, which is all that counts.  However, we
   241 	 * do re-calculate the time just in case the menu had no time limit.
   242 	 */
   243 	float fVoteDelay = sm_vote_delay.GetFloat();
   244 	if (fVoteDelay < 1.0)
   245 	{
   246 		g_next_vote = 0.0;
   247 	} else {
   248 		g_next_vote = gpGlobals->curtime + fVoteDelay;
   249 	}
   250 
   251 	if (m_bCancelled)
   252 	{
   253 		/* If we were cancelled, don't bother tabulating anything.
   254 		 * Reset just in case someone tries to redraw, which means
   255 		 * we need to save our states.
   256 		 */
   257 		IBaseMenu *menu = m_pCurMenu;
   258 		IMenuHandler *handler = m_pHandler;
   259 		InternalReset();
   260 		handler->OnMenuVoteCancel(menu, VoteCancel_Generic);
   261 		handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   262 		return;
   263 	}
   264 
   265 	menu_vote_result_t vote;
   266 	menu_vote_result_t::menu_client_vote_t client_vote[256];
   267 	menu_vote_result_t::menu_item_vote_t item_vote[256];
   268 
   269 	memset(&vote, 0, sizeof(vote));
   270 
   271 	/* Build the item list */
   272 	for (unsigned int i=0; i<m_Items; i++)
   273 	{
   274 		if (m_Votes[i] > 0)
   275 		{
   276 			item_vote[vote.num_items].count = m_Votes[i];
   277 			item_vote[vote.num_items].item = i;
   278 			vote.num_votes += m_Votes[i];
   279 			vote.num_items++;
   280 		}
   281 	}
   282 	vote.item_list = item_vote;
   283 
   284 	if (!vote.num_votes)
   285 	{
   286 		IBaseMenu *menu = m_pCurMenu;
   287 		IMenuHandler *handler = m_pHandler;
   288 		InternalReset();
   289 		handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes);
   290 		handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   291 		return;
   292 	}
   293 
   294 	/* Build the client list */
   295 	for (int i=1; i<=gpGlobals->maxClients; i++)
   296 	{
   297 		if (m_ClientVotes[i] >= -1)
   298 		{
   299 			client_vote[vote.num_clients].client = i;
   300 			client_vote[vote.num_clients].item = m_ClientVotes[i];
   301 			vote.num_clients++;
   302 		}
   303 	}
   304 	vote.client_list = client_vote;
   305 
   306 	/* Sort the item list descending like we promised */
   307 	qsort(item_vote,
   308 		vote.num_items,
   309 		sizeof(menu_vote_result_t::menu_item_vote_t),
   310 		SortVoteItems);
   311 
   312 	/* Save states, then clear what we've saved.
   313 	 * This makes us re-entrant, which is always the safe way to go.
   314 	 */
   315 	IBaseMenu *menu = m_pCurMenu;
   316 	IMenuHandler *handler = m_pHandler;
   317 	InternalReset();
   318 
   319 	/* Send vote info */
   320 	handler->OnMenuVoteResults(menu, &vote);
   321 	handler->OnMenuEnd(menu, MenuEnd_VotingDone);
   322 }
   323 
   324 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
   325 {
   326 	m_Clients++;
   327 }
   328 
   329 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
   330 {
   331 	DecrementPlayerCount();
   332 }
   333 
   334 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
   335 {
   336 	m_pHandler->OnMenuCancel(menu, client, reason);
   337 }
   338 
   339 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
   340 {
   341 	m_ClientVotes[client] = -1;
   342 	m_pHandler->OnMenuDisplay(menu, client, display);
   343 }
   344 
   345 unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
   346 {
   347 	return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
   348 }
   349 
   350 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
   351 {
   352 	m_pHandler->OnMenuDrawItem(menu, client, item, style);
   353 }
   354 
   355 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
   356 {
   357 	/* Check by our item count, NOT the vote array size */
   358 	if (item < m_Items)
   359 	{
   360 		m_ClientVotes[client] = item;
   361 		m_Votes[item]++;
   362 		m_NumVotes++;
   363 	}
   364 
   365 	m_pHandler->OnMenuSelect(menu, client, item);
   366 }
   367 
   368 void VoteMenuHandler::InternalReset()
   369 {
   370 	m_Clients = 0;
   371 	m_Items = 0;
   372 	m_bStarted = false;
   373 	m_pCurMenu = NULL;
   374 	m_NumVotes = 0;
   375 	m_bCancelled = false;
   376 	m_pHandler = NULL;
   377 }
   378 
   379 void VoteMenuHandler::CancelVoting()
   380 {
   381 	if (m_bCancelled || !m_pCurMenu)
   382 	{
   383 		return;
   384 	}
   385 	m_bCancelled = true;
   386 	m_pCurMenu->Cancel();
   387 }
   388 
   389 IBaseMenu *VoteMenuHandler::GetCurrentMenu()
   390 {
   391 	return m_pCurMenu;
   392 }
   393 
   394 bool VoteMenuHandler::IsCancelling()
   395 {
   396 	return m_bCancelled;
   397 }