core/MenuVoting.cpp
author Scott Ehlert <ds@alliedmods.net>
Sun Mar 30 07:00:22 2008 +0000 (2008-03-30)
changeset 1952 b7aa2b626807
child 2056 f1fcde46ac7c
permissions -rw-r--r--
Spring Cleaning, Part Ichi (1)
Various minor things done to project files
Updated sample extension project file and updated makefile to the new unified version (more changes likely on the way)
Updated regex project file and makefile
     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 */
   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 }