core/MenuVoting.cpp
author David Anderson <dvander@alliedmods.net>
Sun Oct 21 17:58:35 2007 +0000 (2007-10-21)
changeset 1620 fd5c786ca8e5
parent 1541 e46ad208b9b4
child 1947 e73a1e51833b
permissions -rw-r--r--
atcprintf() is now fully re-entrant, meaning translations can be used from inside translations
     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 #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 */
   117 	int item;
   118 	if ((item = m_ClientVotes[client]) >= 0)
   119 	{
   120 		assert((unsigned)item < m_Items);
   121 		assert(m_Votes[item] > 0);
   122 		m_Votes[item]--;
   123 		m_ClientVotes[client] = -1;
   124 	}
   125 }
   126 
   127 bool VoteMenuHandler::IsVoteInProgress()
   128 {
   129 	return (m_pCurMenu != NULL);
   130 }
   131 
   132 bool VoteMenuHandler::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags/* =0 */)
   133 {
   134 	if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags))
   135 	{
   136 		return false;
   137 	}
   138 
   139 	float fVoteDelay = sm_vote_delay.GetFloat();
   140 	if (fVoteDelay < 1.0)
   141 	{
   142 		g_next_vote = 0.0;
   143 	} else {
   144 		/* This little trick breaks for infinite votes!
   145 		 * However, we just ignore that since those 1) shouldn't exist and 
   146 		 * 2) people must be checking IsVoteInProgress() beforehand anyway.
   147 		 */
   148 		g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time;
   149 	}
   150 
   151 	for (unsigned int i=0; i<num_clients; i++)
   152 	{
   153 		menu->Display(clients[i], max_time, this);
   154 	}
   155 
   156 	StartVoting();
   157 
   158 	return true;
   159 }
   160 
   161 bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, 
   162 									   IMenuHandler *handler,
   163 									   unsigned int time,
   164 									   unsigned int flags)
   165 {
   166 	if (IsVoteInProgress())
   167 	{
   168 		return false;
   169 	}
   170 
   171 	InternalReset();
   172 
   173 	/* Mark all clients as not voting */
   174 	for (int i=1; i<=gpGlobals->maxClients; i++)
   175 	{
   176 		m_ClientVotes[i] = -2;
   177 	}
   178 
   179 	m_Items = menu->GetItemCount();
   180 
   181 	if (m_Votes.size() < (size_t)m_Items)
   182 	{
   183 		/* Only clear the items we need to... */
   184 		size_t size = m_Votes.size();
   185 		for (size_t i=0; i<size; i++)
   186 		{
   187 			m_Votes[i] = 0;
   188 		}
   189 		m_Votes.resize(m_Items, 0);
   190 	} else {
   191 		for (unsigned int i=0; i<m_Items; i++)
   192 		{
   193 			m_Votes[i] = 0;
   194 		}
   195 	}
   196 
   197 	m_pCurMenu = menu;
   198 	m_VoteTime = time;
   199 	m_VoteFlags = flags;
   200 	m_pHandler = handler;
   201 
   202 	m_pHandler->OnMenuStart(m_pCurMenu);
   203 
   204 	return true;
   205 }
   206 
   207 void VoteMenuHandler::StartVoting()
   208 {
   209 	if (!m_pCurMenu)
   210 	{
   211 		return;
   212 	}
   213 
   214 	m_bStarted = true;
   215 
   216 	m_pHandler->OnMenuVoteStart(m_pCurMenu);
   217 
   218 	/* By now we know how many clients were set.  
   219 	 * If there are none, we should end IMMEDIATELY.
   220 	 */
   221 	if (m_Clients == 0)
   222 	{
   223 		EndVoting();
   224 	}
   225 }
   226 
   227 void VoteMenuHandler::DecrementPlayerCount()
   228 {
   229 	assert(m_Clients > 0);
   230 
   231 	m_Clients--;
   232 
   233 	if (m_bStarted && m_Clients == 0)
   234 	{
   235 		EndVoting();
   236 	}
   237 }
   238 
   239 int SortVoteItems(const void *item1, const void *item2)
   240 {
   241 	return ((menu_vote_result_t::menu_item_vote_t *)item2)->count 
   242 		- ((menu_vote_result_t::menu_item_vote_t *)item1)->count;
   243 }
   244 
   245 void VoteMenuHandler::EndVoting()
   246 {
   247 	/* Set when the next delay ends.  We ignore cancellation because a menu
   248 	 * was, at one point, displayed, which is all that counts.  However, we
   249 	 * do re-calculate the time just in case the menu had no time limit.
   250 	 */
   251 	float fVoteDelay = sm_vote_delay.GetFloat();
   252 	if (fVoteDelay < 1.0)
   253 	{
   254 		g_next_vote = 0.0;
   255 	} else {
   256 		g_next_vote = gpGlobals->curtime + fVoteDelay;
   257 	}
   258 
   259 	if (m_bCancelled)
   260 	{
   261 		/* If we were cancelled, don't bother tabulating anything.
   262 		 * Reset just in case someone tries to redraw, which means
   263 		 * we need to save our states.
   264 		 */
   265 		IBaseMenu *menu = m_pCurMenu;
   266 		IMenuHandler *handler = m_pHandler;
   267 		InternalReset();
   268 		handler->OnMenuVoteCancel(menu, VoteCancel_Generic);
   269 		handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   270 		return;
   271 	}
   272 
   273 	menu_vote_result_t vote;
   274 	menu_vote_result_t::menu_client_vote_t client_vote[256];
   275 	menu_vote_result_t::menu_item_vote_t item_vote[256];
   276 
   277 	memset(&vote, 0, sizeof(vote));
   278 
   279 	/* Build the item list */
   280 	for (unsigned int i=0; i<m_Items; i++)
   281 	{
   282 		if (m_Votes[i] > 0)
   283 		{
   284 			item_vote[vote.num_items].count = m_Votes[i];
   285 			item_vote[vote.num_items].item = i;
   286 			vote.num_votes += m_Votes[i];
   287 			vote.num_items++;
   288 		}
   289 	}
   290 	vote.item_list = item_vote;
   291 
   292 	if (!vote.num_votes)
   293 	{
   294 		IBaseMenu *menu = m_pCurMenu;
   295 		IMenuHandler *handler = m_pHandler;
   296 		InternalReset();
   297 		handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes);
   298 		handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
   299 		return;
   300 	}
   301 
   302 	/* Build the client list */
   303 	for (int i=1; i<=gpGlobals->maxClients; i++)
   304 	{
   305 		if (m_ClientVotes[i] >= -1)
   306 		{
   307 			client_vote[vote.num_clients].client = i;
   308 			client_vote[vote.num_clients].item = m_ClientVotes[i];
   309 			vote.num_clients++;
   310 		}
   311 	}
   312 	vote.client_list = client_vote;
   313 
   314 	/* Sort the item list descending like we promised */
   315 	qsort(item_vote,
   316 		vote.num_items,
   317 		sizeof(menu_vote_result_t::menu_item_vote_t),
   318 		SortVoteItems);
   319 
   320 	/* Save states, then clear what we've saved.
   321 	 * This makes us re-entrant, which is always the safe way to go.
   322 	 */
   323 	IBaseMenu *menu = m_pCurMenu;
   324 	IMenuHandler *handler = m_pHandler;
   325 	InternalReset();
   326 
   327 	/* Send vote info */
   328 	handler->OnMenuVoteResults(menu, &vote);
   329 	handler->OnMenuEnd(menu, MenuEnd_VotingDone);
   330 }
   331 
   332 void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
   333 {
   334 	m_Clients++;
   335 }
   336 
   337 void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
   338 {
   339 	DecrementPlayerCount();
   340 }
   341 
   342 void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
   343 {
   344 	m_pHandler->OnMenuCancel(menu, client, reason);
   345 }
   346 
   347 void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
   348 {
   349 	m_ClientVotes[client] = -1;
   350 	m_pHandler->OnMenuDisplay(menu, client, display);
   351 }
   352 
   353 unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
   354 {
   355 	return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
   356 }
   357 
   358 void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
   359 {
   360 	m_pHandler->OnMenuDrawItem(menu, client, item, style);
   361 }
   362 
   363 void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
   364 {
   365 	/* Check by our item count, NOT the vote array size */
   366 	if (item < m_Items)
   367 	{
   368 		m_ClientVotes[client] = item;
   369 		m_Votes[item]++;
   370 		m_NumVotes++;
   371 	}
   372 
   373 	m_pHandler->OnMenuSelect(menu, client, item);
   374 }
   375 
   376 void VoteMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
   377 {
   378 	if (m_pHandler->GetMenuAPIVersion2() >= 13)
   379 	{
   380 		m_pHandler->OnMenuSelect2(menu, client, item, item_on_page);
   381 	}
   382 }
   383 
   384 void VoteMenuHandler::InternalReset()
   385 {
   386 	m_Clients = 0;
   387 	m_Items = 0;
   388 	m_bStarted = false;
   389 	m_pCurMenu = NULL;
   390 	m_NumVotes = 0;
   391 	m_bCancelled = false;
   392 	m_pHandler = NULL;
   393 }
   394 
   395 void VoteMenuHandler::CancelVoting()
   396 {
   397 	if (m_bCancelled || !m_pCurMenu)
   398 	{
   399 		return;
   400 	}
   401 	m_bCancelled = true;
   402 	m_pCurMenu->Cancel();
   403 }
   404 
   405 IBaseMenu *VoteMenuHandler::GetCurrentMenu()
   406 {
   407 	return m_pCurMenu;
   408 }
   409 
   410 bool VoteMenuHandler::IsCancelling()
   411 {
   412 	return m_bCancelled;
   413 }