1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/core/MenuVoting.cpp Sun Jul 06 04:49:55 2008 +0000
1.3 @@ -0,0 +1,491 @@
1.4 +/**
1.5 + * vim: set ts=4 :
1.6 + * =============================================================================
1.7 + * SourceMod
1.8 + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
1.9 + * =============================================================================
1.10 + *
1.11 + * This program is free software; you can redistribute it and/or modify it under
1.12 + * the terms of the GNU General Public License, version 3.0, as published by the
1.13 + * Free Software Foundation.
1.14 + *
1.15 + * This program is distributed in the hope that it will be useful, but WITHOUT
1.16 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.17 + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.18 + * details.
1.19 + *
1.20 + * You should have received a copy of the GNU General Public License along with
1.21 + * this program. If not, see <http://www.gnu.org/licenses/>.
1.22 + *
1.23 + * As a special exception, AlliedModders LLC gives you permission to link the
1.24 + * code of this program (as well as its derivative works) to "Half-Life 2," the
1.25 + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
1.26 + * by the Valve Corporation. You must obey the GNU General Public License in
1.27 + * all respects for all other code used. Additionally, AlliedModders LLC grants
1.28 + * this exception to all derivative works. AlliedModders LLC defines further
1.29 + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
1.30 + * or <http://www.sourcemod.net/license.php>.
1.31 + *
1.32 + * Version: $Id$
1.33 + */
1.34 +
1.35 +#include <string.h>
1.36 +#include <stdlib.h>
1.37 +#include "MenuVoting.h"
1.38 +#include "PlayerManager.h"
1.39 +#include "sourcemm_api.h"
1.40 +
1.41 +float g_next_vote = 0.0f;
1.42 +
1.43 +#if defined ORANGEBOX_BUILD
1.44 +void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue);
1.45 +#else
1.46 +void OnVoteDelayChange(ConVar *cvar, const char *value);
1.47 +#endif
1.48 +ConVar sm_vote_delay("sm_vote_delay",
1.49 + "30",
1.50 + 0,
1.51 + "Sets the recommended time in between public votes",
1.52 + false,
1.53 + 0.0,
1.54 + false,
1.55 + 0.0,
1.56 + OnVoteDelayChange);
1.57 +
1.58 +#if defined ORANGEBOX_BUILD
1.59 +void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue)
1.60 +#else
1.61 +void OnVoteDelayChange(ConVar *cvar, const char *value)
1.62 +#endif
1.63 +{
1.64 + /* See if the new vote delay isn't something we need to account for */
1.65 + if (sm_vote_delay.GetFloat() < 1.0f)
1.66 + {
1.67 + g_next_vote = 0.0f;
1.68 + return;
1.69 + }
1.70 +
1.71 + /* If there was never a last vote, ignore this change */
1.72 + if (g_next_vote < 0.1f)
1.73 + {
1.74 + return;
1.75 + }
1.76 +
1.77 + /* Subtract the original value, then add the new one. */
1.78 + g_next_vote -= (float)atof(value);
1.79 + g_next_vote += sm_vote_delay.GetFloat();
1.80 +}
1.81 +
1.82 +unsigned int VoteMenuHandler::GetRemainingVoteDelay()
1.83 +{
1.84 + if (g_next_vote <= gpGlobals->curtime)
1.85 + {
1.86 + return 0;
1.87 + }
1.88 +
1.89 + return (unsigned int)(g_next_vote - gpGlobals->curtime);
1.90 +}
1.91 +
1.92 +void VoteMenuHandler::OnSourceModAllInitialized()
1.93 +{
1.94 + g_Players.AddClientListener(this);
1.95 +}
1.96 +
1.97 +void VoteMenuHandler::OnSourceModShutdown()
1.98 +{
1.99 + g_Players.RemoveClientListener(this);
1.100 +}
1.101 +
1.102 +void VoteMenuHandler::OnSourceModLevelChange(const char *mapName)
1.103 +{
1.104 + g_next_vote = 0.0f;
1.105 +}
1.106 +
1.107 +unsigned int VoteMenuHandler::GetMenuAPIVersion2()
1.108 +{
1.109 + return m_pHandler->GetMenuAPIVersion2();
1.110 +}
1.111 +
1.112 +void VoteMenuHandler::OnClientDisconnected(int client)
1.113 +{
1.114 + if (!IsVoteInProgress())
1.115 + {
1.116 + return;
1.117 + }
1.118 +
1.119 + /* Wipe out their vote if they had one. We have to make sure the the the
1.120 + * newly connected client is not allowed to vote.
1.121 + */
1.122 + int item;
1.123 + if ((item = m_ClientVotes[client]) >= -1)
1.124 + {
1.125 + if (item >= 0)
1.126 + {
1.127 + assert((unsigned)item < m_Items);
1.128 + assert(m_Votes[item] > 0);
1.129 + m_Votes[item]--;
1.130 + }
1.131 + m_ClientVotes[client] = -2;
1.132 + }
1.133 +}
1.134 +
1.135 +bool VoteMenuHandler::IsVoteInProgress()
1.136 +{
1.137 + return (m_pCurMenu != NULL);
1.138 +}
1.139 +
1.140 +bool VoteMenuHandler::StartVote(IBaseMenu *menu,
1.141 + unsigned int num_clients,
1.142 + int clients[],
1.143 + unsigned int max_time,
1.144 + unsigned int flags/* =0 */)
1.145 +{
1.146 + if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags))
1.147 + {
1.148 + return false;
1.149 + }
1.150 +
1.151 + /* Note: we can use game time and not universal time because
1.152 + * if we're voting then players are in-game.
1.153 + */
1.154 +
1.155 + float fVoteDelay = sm_vote_delay.GetFloat();
1.156 + if (fVoteDelay < 1.0)
1.157 + {
1.158 + g_next_vote = 0.0;
1.159 + }
1.160 + else
1.161 + {
1.162 + /* This little trick breaks for infinite votes!
1.163 + * However, we just ignore that since those 1) shouldn't exist and
1.164 + * 2) people must be checking IsVoteInProgress() beforehand anyway.
1.165 + */
1.166 + g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time;
1.167 + }
1.168 +
1.169 + m_fStartTime = gpGlobals->curtime;
1.170 + m_nMenuTime = max_time;
1.171 +
1.172 + for (unsigned int i=0; i<num_clients; i++)
1.173 + {
1.174 + if (clients[i] < 1 || clients[i] > 256)
1.175 + {
1.176 + continue;
1.177 + }
1.178 + menu->Display(clients[i], max_time, this);
1.179 + }
1.180 +
1.181 + StartVoting();
1.182 +
1.183 + return true;
1.184 +}
1.185 +
1.186 +bool VoteMenuHandler::IsClientInVotePool(int client)
1.187 +{
1.188 + if (client < 1
1.189 + || client > g_Players.MaxClients()
1.190 + || m_pCurMenu == NULL)
1.191 + {
1.192 + return false;
1.193 + }
1.194 +
1.195 + return (m_ClientVotes[client] > -2);
1.196 +}
1.197 +
1.198 +bool VoteMenuHandler::GetClientVoteChoice(int client, unsigned int *pItem)
1.199 +{
1.200 + if (!IsClientInVotePool(client)
1.201 + || m_ClientVotes[client] == -1)
1.202 + {
1.203 + return false;
1.204 + }
1.205 +
1.206 + *pItem = m_ClientVotes[client];
1.207 +
1.208 + return true;
1.209 +}
1.210 +
1.211 +bool VoteMenuHandler::RedrawToClient(int client)
1.212 +{
1.213 + unsigned int time_limit;
1.214 +
1.215 + if (!IsClientInVotePool(client))
1.216 + {
1.217 + return false;
1.218 + }
1.219 +
1.220 + if (m_nMenuTime == MENU_TIME_FOREVER)
1.221 + {
1.222 + time_limit = m_nMenuTime;
1.223 + }
1.224 + else
1.225 + {
1.226 + time_limit = (int)((float)m_nMenuTime - (gpGlobals->curtime - m_fStartTime));
1.227 +
1.228 + /* Make sure this doesn't round to zero */
1.229 + if (time_limit == MENU_TIME_FOREVER)
1.230 + {
1.231 + time_limit = 1;
1.232 + }
1.233 + }
1.234 +
1.235 + return m_pCurMenu->Display(client, time_limit, this);
1.236 +}
1.237 +
1.238 +bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu,
1.239 + IMenuHandler *handler,
1.240 + unsigned int time,
1.241 + unsigned int flags)
1.242 +{
1.243 + if (IsVoteInProgress())
1.244 + {
1.245 + return false;
1.246 + }
1.247 +
1.248 + InternalReset();
1.249 +
1.250 + /* Mark all clients as not voting */
1.251 + for (int i=1; i<=gpGlobals->maxClients; i++)
1.252 + {
1.253 + m_ClientVotes[i] = -2;
1.254 + }
1.255 +
1.256 + m_Items = menu->GetItemCount();
1.257 +
1.258 + if (m_Votes.size() < (size_t)m_Items)
1.259 + {
1.260 + /* Only clear the items we need to... */
1.261 + size_t size = m_Votes.size();
1.262 + for (size_t i=0; i<size; i++)
1.263 + {
1.264 + m_Votes[i] = 0;
1.265 + }
1.266 + m_Votes.resize(m_Items, 0);
1.267 + }
1.268 + else
1.269 + {
1.270 + for (unsigned int i=0; i<m_Items; i++)
1.271 + {
1.272 + m_Votes[i] = 0;
1.273 + }
1.274 + }
1.275 +
1.276 + m_pCurMenu = menu;
1.277 + m_VoteTime = time;
1.278 + m_VoteFlags = flags;
1.279 + m_pHandler = handler;
1.280 +
1.281 + m_pHandler->OnMenuStart(m_pCurMenu);
1.282 +
1.283 + return true;
1.284 +}
1.285 +
1.286 +void VoteMenuHandler::StartVoting()
1.287 +{
1.288 + if (!m_pCurMenu)
1.289 + {
1.290 + return;
1.291 + }
1.292 +
1.293 + m_bStarted = true;
1.294 +
1.295 + m_pHandler->OnMenuVoteStart(m_pCurMenu);
1.296 +
1.297 + /* By now we know how many clients were set.
1.298 + * If there are none, we should end IMMEDIATELY.
1.299 + */
1.300 + if (m_Clients == 0)
1.301 + {
1.302 + EndVoting();
1.303 + }
1.304 +}
1.305 +
1.306 +void VoteMenuHandler::DecrementPlayerCount()
1.307 +{
1.308 + assert(m_Clients > 0);
1.309 +
1.310 + m_Clients--;
1.311 +
1.312 + if (m_bStarted && m_Clients == 0)
1.313 + {
1.314 + EndVoting();
1.315 + }
1.316 +}
1.317 +
1.318 +int SortVoteItems(const void *item1, const void *item2)
1.319 +{
1.320 + return ((menu_vote_result_t::menu_item_vote_t *)item2)->count
1.321 + - ((menu_vote_result_t::menu_item_vote_t *)item1)->count;
1.322 +}
1.323 +
1.324 +void VoteMenuHandler::EndVoting()
1.325 +{
1.326 + /* Set when the next delay ends. We ignore cancellation because a menu
1.327 + * was, at one point, displayed, which is all that counts. However, we
1.328 + * do re-calculate the time just in case the menu had no time limit.
1.329 + */
1.330 + float fVoteDelay = sm_vote_delay.GetFloat();
1.331 + if (fVoteDelay < 1.0)
1.332 + {
1.333 + g_next_vote = 0.0;
1.334 + }
1.335 + else
1.336 + {
1.337 + g_next_vote = gpGlobals->curtime + fVoteDelay;
1.338 + }
1.339 +
1.340 + if (m_bCancelled)
1.341 + {
1.342 + /* If we were cancelled, don't bother tabulating anything.
1.343 + * Reset just in case someone tries to redraw, which means
1.344 + * we need to save our states.
1.345 + */
1.346 + IBaseMenu *menu = m_pCurMenu;
1.347 + IMenuHandler *handler = m_pHandler;
1.348 + InternalReset();
1.349 + handler->OnMenuVoteCancel(menu, VoteCancel_Generic);
1.350 + handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
1.351 + return;
1.352 + }
1.353 +
1.354 + menu_vote_result_t vote;
1.355 + menu_vote_result_t::menu_client_vote_t client_vote[256];
1.356 + menu_vote_result_t::menu_item_vote_t item_vote[256];
1.357 +
1.358 + memset(&vote, 0, sizeof(vote));
1.359 +
1.360 + /* Build the item list */
1.361 + for (unsigned int i=0; i<m_Items; i++)
1.362 + {
1.363 + if (m_Votes[i] > 0)
1.364 + {
1.365 + item_vote[vote.num_items].count = m_Votes[i];
1.366 + item_vote[vote.num_items].item = i;
1.367 + vote.num_votes += m_Votes[i];
1.368 + vote.num_items++;
1.369 + }
1.370 + }
1.371 + vote.item_list = item_vote;
1.372 +
1.373 + if (!vote.num_votes)
1.374 + {
1.375 + IBaseMenu *menu = m_pCurMenu;
1.376 + IMenuHandler *handler = m_pHandler;
1.377 + InternalReset();
1.378 + handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes);
1.379 + handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
1.380 + return;
1.381 + }
1.382 +
1.383 + /* Build the client list */
1.384 + for (int i=1; i<=gpGlobals->maxClients; i++)
1.385 + {
1.386 + if (m_ClientVotes[i] >= -1)
1.387 + {
1.388 + client_vote[vote.num_clients].client = i;
1.389 + client_vote[vote.num_clients].item = m_ClientVotes[i];
1.390 + vote.num_clients++;
1.391 + }
1.392 + }
1.393 + vote.client_list = client_vote;
1.394 +
1.395 + /* Sort the item list descending like we promised */
1.396 + qsort(item_vote,
1.397 + vote.num_items,
1.398 + sizeof(menu_vote_result_t::menu_item_vote_t),
1.399 + SortVoteItems);
1.400 +
1.401 + /* Save states, then clear what we've saved.
1.402 + * This makes us re-entrant, which is always the safe way to go.
1.403 + */
1.404 + IBaseMenu *menu = m_pCurMenu;
1.405 + IMenuHandler *handler = m_pHandler;
1.406 + InternalReset();
1.407 +
1.408 + /* Send vote info */
1.409 + handler->OnMenuVoteResults(menu, &vote);
1.410 + handler->OnMenuEnd(menu, MenuEnd_VotingDone);
1.411 +}
1.412 +
1.413 +void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
1.414 +{
1.415 + m_Clients++;
1.416 +}
1.417 +
1.418 +void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
1.419 +{
1.420 + DecrementPlayerCount();
1.421 +}
1.422 +
1.423 +void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
1.424 +{
1.425 + m_pHandler->OnMenuCancel(menu, client, reason);
1.426 +}
1.427 +
1.428 +void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
1.429 +{
1.430 + m_ClientVotes[client] = -1;
1.431 + m_pHandler->OnMenuDisplay(menu, client, display);
1.432 +}
1.433 +
1.434 +unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
1.435 +{
1.436 + return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
1.437 +}
1.438 +
1.439 +void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
1.440 +{
1.441 + m_pHandler->OnMenuDrawItem(menu, client, item, style);
1.442 +}
1.443 +
1.444 +void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
1.445 +{
1.446 + /* Check by our item count, NOT the vote array size */
1.447 + if (item < m_Items)
1.448 + {
1.449 + m_ClientVotes[client] = item;
1.450 + m_Votes[item]++;
1.451 + m_NumVotes++;
1.452 + }
1.453 +
1.454 + m_pHandler->OnMenuSelect(menu, client, item);
1.455 +}
1.456 +
1.457 +void VoteMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
1.458 +{
1.459 + if (m_pHandler->GetMenuAPIVersion2() >= 13)
1.460 + {
1.461 + m_pHandler->OnMenuSelect2(menu, client, item, item_on_page);
1.462 + }
1.463 +}
1.464 +
1.465 +void VoteMenuHandler::InternalReset()
1.466 +{
1.467 + m_Clients = 0;
1.468 + m_Items = 0;
1.469 + m_bStarted = false;
1.470 + m_pCurMenu = NULL;
1.471 + m_NumVotes = 0;
1.472 + m_bCancelled = false;
1.473 + m_pHandler = NULL;
1.474 +}
1.475 +
1.476 +void VoteMenuHandler::CancelVoting()
1.477 +{
1.478 + if (m_bCancelled || !m_pCurMenu)
1.479 + {
1.480 + return;
1.481 + }
1.482 + m_bCancelled = true;
1.483 + m_pCurMenu->Cancel();
1.484 +}
1.485 +
1.486 +IBaseMenu *VoteMenuHandler::GetCurrentMenu()
1.487 +{
1.488 + return m_pCurMenu;
1.489 +}
1.490 +
1.491 +bool VoteMenuHandler::IsCancelling()
1.492 +{
1.493 + return m_bCancelled;
1.494 +}