core/MenuVoting.cpp
author David Anderson <dvander@alliedmods.net>
Wed Sep 26 14:38:40 2007 +0000 (2007-09-26)
changeset 1488 5562086c65c8
parent 1474 a3b73df84d88
child 1541 e46ad208b9b4
permissions -rw-r--r--
initial import of UNTESTED, UNFINISHED topmenus extension
     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::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
   369 {
   370 	if (m_pHandler->GetMenuAPIVersion2() >= 13)
   371 	{
   372 		m_pHandler->OnMenuSelect2(menu, client, item, item_on_page);
   373 	}
   374 }
   375 
   376 void VoteMenuHandler::InternalReset()
   377 {
   378 	m_Clients = 0;
   379 	m_Items = 0;
   380 	m_bStarted = false;
   381 	m_pCurMenu = NULL;
   382 	m_NumVotes = 0;
   383 	m_bCancelled = false;
   384 	m_pHandler = NULL;
   385 }
   386 
   387 void VoteMenuHandler::CancelVoting()
   388 {
   389 	if (m_bCancelled || !m_pCurMenu)
   390 	{
   391 		return;
   392 	}
   393 	m_bCancelled = true;
   394 	m_pCurMenu->Cancel();
   395 }
   396 
   397 IBaseMenu *VoteMenuHandler::GetCurrentMenu()
   398 {
   399 	return m_pCurMenu;
   400 }
   401 
   402 bool VoteMenuHandler::IsCancelling()
   403 {
   404 	return m_bCancelled;
   405 }