1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/core/MenuVoting.cpp Sun Mar 30 07:00:22 2008 +0000
1.3 @@ -0,0 +1,413 @@
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 */
1.120 + int item;
1.121 + if ((item = m_ClientVotes[client]) >= 0)
1.122 + {
1.123 + assert((unsigned)item < m_Items);
1.124 + assert(m_Votes[item] > 0);
1.125 + m_Votes[item]--;
1.126 + m_ClientVotes[client] = -1;
1.127 + }
1.128 +}
1.129 +
1.130 +bool VoteMenuHandler::IsVoteInProgress()
1.131 +{
1.132 + return (m_pCurMenu != NULL);
1.133 +}
1.134 +
1.135 +bool VoteMenuHandler::StartVote(IBaseMenu *menu, unsigned int num_clients, int clients[], unsigned int max_time, unsigned int flags/* =0 */)
1.136 +{
1.137 + if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags))
1.138 + {
1.139 + return false;
1.140 + }
1.141 +
1.142 + float fVoteDelay = sm_vote_delay.GetFloat();
1.143 + if (fVoteDelay < 1.0)
1.144 + {
1.145 + g_next_vote = 0.0;
1.146 + } else {
1.147 + /* This little trick breaks for infinite votes!
1.148 + * However, we just ignore that since those 1) shouldn't exist and
1.149 + * 2) people must be checking IsVoteInProgress() beforehand anyway.
1.150 + */
1.151 + g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time;
1.152 + }
1.153 +
1.154 + for (unsigned int i=0; i<num_clients; i++)
1.155 + {
1.156 + menu->Display(clients[i], max_time, this);
1.157 + }
1.158 +
1.159 + StartVoting();
1.160 +
1.161 + return true;
1.162 +}
1.163 +
1.164 +bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu,
1.165 + IMenuHandler *handler,
1.166 + unsigned int time,
1.167 + unsigned int flags)
1.168 +{
1.169 + if (IsVoteInProgress())
1.170 + {
1.171 + return false;
1.172 + }
1.173 +
1.174 + InternalReset();
1.175 +
1.176 + /* Mark all clients as not voting */
1.177 + for (int i=1; i<=gpGlobals->maxClients; i++)
1.178 + {
1.179 + m_ClientVotes[i] = -2;
1.180 + }
1.181 +
1.182 + m_Items = menu->GetItemCount();
1.183 +
1.184 + if (m_Votes.size() < (size_t)m_Items)
1.185 + {
1.186 + /* Only clear the items we need to... */
1.187 + size_t size = m_Votes.size();
1.188 + for (size_t i=0; i<size; i++)
1.189 + {
1.190 + m_Votes[i] = 0;
1.191 + }
1.192 + m_Votes.resize(m_Items, 0);
1.193 + } else {
1.194 + for (unsigned int i=0; i<m_Items; i++)
1.195 + {
1.196 + m_Votes[i] = 0;
1.197 + }
1.198 + }
1.199 +
1.200 + m_pCurMenu = menu;
1.201 + m_VoteTime = time;
1.202 + m_VoteFlags = flags;
1.203 + m_pHandler = handler;
1.204 +
1.205 + m_pHandler->OnMenuStart(m_pCurMenu);
1.206 +
1.207 + return true;
1.208 +}
1.209 +
1.210 +void VoteMenuHandler::StartVoting()
1.211 +{
1.212 + if (!m_pCurMenu)
1.213 + {
1.214 + return;
1.215 + }
1.216 +
1.217 + m_bStarted = true;
1.218 +
1.219 + m_pHandler->OnMenuVoteStart(m_pCurMenu);
1.220 +
1.221 + /* By now we know how many clients were set.
1.222 + * If there are none, we should end IMMEDIATELY.
1.223 + */
1.224 + if (m_Clients == 0)
1.225 + {
1.226 + EndVoting();
1.227 + }
1.228 +}
1.229 +
1.230 +void VoteMenuHandler::DecrementPlayerCount()
1.231 +{
1.232 + assert(m_Clients > 0);
1.233 +
1.234 + m_Clients--;
1.235 +
1.236 + if (m_bStarted && m_Clients == 0)
1.237 + {
1.238 + EndVoting();
1.239 + }
1.240 +}
1.241 +
1.242 +int SortVoteItems(const void *item1, const void *item2)
1.243 +{
1.244 + return ((menu_vote_result_t::menu_item_vote_t *)item2)->count
1.245 + - ((menu_vote_result_t::menu_item_vote_t *)item1)->count;
1.246 +}
1.247 +
1.248 +void VoteMenuHandler::EndVoting()
1.249 +{
1.250 + /* Set when the next delay ends. We ignore cancellation because a menu
1.251 + * was, at one point, displayed, which is all that counts. However, we
1.252 + * do re-calculate the time just in case the menu had no time limit.
1.253 + */
1.254 + float fVoteDelay = sm_vote_delay.GetFloat();
1.255 + if (fVoteDelay < 1.0)
1.256 + {
1.257 + g_next_vote = 0.0;
1.258 + } else {
1.259 + g_next_vote = gpGlobals->curtime + fVoteDelay;
1.260 + }
1.261 +
1.262 + if (m_bCancelled)
1.263 + {
1.264 + /* If we were cancelled, don't bother tabulating anything.
1.265 + * Reset just in case someone tries to redraw, which means
1.266 + * we need to save our states.
1.267 + */
1.268 + IBaseMenu *menu = m_pCurMenu;
1.269 + IMenuHandler *handler = m_pHandler;
1.270 + InternalReset();
1.271 + handler->OnMenuVoteCancel(menu, VoteCancel_Generic);
1.272 + handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
1.273 + return;
1.274 + }
1.275 +
1.276 + menu_vote_result_t vote;
1.277 + menu_vote_result_t::menu_client_vote_t client_vote[256];
1.278 + menu_vote_result_t::menu_item_vote_t item_vote[256];
1.279 +
1.280 + memset(&vote, 0, sizeof(vote));
1.281 +
1.282 + /* Build the item list */
1.283 + for (unsigned int i=0; i<m_Items; i++)
1.284 + {
1.285 + if (m_Votes[i] > 0)
1.286 + {
1.287 + item_vote[vote.num_items].count = m_Votes[i];
1.288 + item_vote[vote.num_items].item = i;
1.289 + vote.num_votes += m_Votes[i];
1.290 + vote.num_items++;
1.291 + }
1.292 + }
1.293 + vote.item_list = item_vote;
1.294 +
1.295 + if (!vote.num_votes)
1.296 + {
1.297 + IBaseMenu *menu = m_pCurMenu;
1.298 + IMenuHandler *handler = m_pHandler;
1.299 + InternalReset();
1.300 + handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes);
1.301 + handler->OnMenuEnd(menu, MenuEnd_VotingCancelled);
1.302 + return;
1.303 + }
1.304 +
1.305 + /* Build the client list */
1.306 + for (int i=1; i<=gpGlobals->maxClients; i++)
1.307 + {
1.308 + if (m_ClientVotes[i] >= -1)
1.309 + {
1.310 + client_vote[vote.num_clients].client = i;
1.311 + client_vote[vote.num_clients].item = m_ClientVotes[i];
1.312 + vote.num_clients++;
1.313 + }
1.314 + }
1.315 + vote.client_list = client_vote;
1.316 +
1.317 + /* Sort the item list descending like we promised */
1.318 + qsort(item_vote,
1.319 + vote.num_items,
1.320 + sizeof(menu_vote_result_t::menu_item_vote_t),
1.321 + SortVoteItems);
1.322 +
1.323 + /* Save states, then clear what we've saved.
1.324 + * This makes us re-entrant, which is always the safe way to go.
1.325 + */
1.326 + IBaseMenu *menu = m_pCurMenu;
1.327 + IMenuHandler *handler = m_pHandler;
1.328 + InternalReset();
1.329 +
1.330 + /* Send vote info */
1.331 + handler->OnMenuVoteResults(menu, &vote);
1.332 + handler->OnMenuEnd(menu, MenuEnd_VotingDone);
1.333 +}
1.334 +
1.335 +void VoteMenuHandler::OnMenuStart(IBaseMenu *menu)
1.336 +{
1.337 + m_Clients++;
1.338 +}
1.339 +
1.340 +void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason)
1.341 +{
1.342 + DecrementPlayerCount();
1.343 +}
1.344 +
1.345 +void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason)
1.346 +{
1.347 + m_pHandler->OnMenuCancel(menu, client, reason);
1.348 +}
1.349 +
1.350 +void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display)
1.351 +{
1.352 + m_ClientVotes[client] = -1;
1.353 + m_pHandler->OnMenuDisplay(menu, client, display);
1.354 +}
1.355 +
1.356 +unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr)
1.357 +{
1.358 + return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr);
1.359 +}
1.360 +
1.361 +void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style)
1.362 +{
1.363 + m_pHandler->OnMenuDrawItem(menu, client, item, style);
1.364 +}
1.365 +
1.366 +void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item)
1.367 +{
1.368 + /* Check by our item count, NOT the vote array size */
1.369 + if (item < m_Items)
1.370 + {
1.371 + m_ClientVotes[client] = item;
1.372 + m_Votes[item]++;
1.373 + m_NumVotes++;
1.374 + }
1.375 +
1.376 + m_pHandler->OnMenuSelect(menu, client, item);
1.377 +}
1.378 +
1.379 +void VoteMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page)
1.380 +{
1.381 + if (m_pHandler->GetMenuAPIVersion2() >= 13)
1.382 + {
1.383 + m_pHandler->OnMenuSelect2(menu, client, item, item_on_page);
1.384 + }
1.385 +}
1.386 +
1.387 +void VoteMenuHandler::InternalReset()
1.388 +{
1.389 + m_Clients = 0;
1.390 + m_Items = 0;
1.391 + m_bStarted = false;
1.392 + m_pCurMenu = NULL;
1.393 + m_NumVotes = 0;
1.394 + m_bCancelled = false;
1.395 + m_pHandler = NULL;
1.396 +}
1.397 +
1.398 +void VoteMenuHandler::CancelVoting()
1.399 +{
1.400 + if (m_bCancelled || !m_pCurMenu)
1.401 + {
1.402 + return;
1.403 + }
1.404 + m_bCancelled = true;
1.405 + m_pCurMenu->Cancel();
1.406 +}
1.407 +
1.408 +IBaseMenu *VoteMenuHandler::GetCurrentMenu()
1.409 +{
1.410 + return m_pCurMenu;
1.411 +}
1.412 +
1.413 +bool VoteMenuHandler::IsCancelling()
1.414 +{
1.415 + return m_bCancelled;
1.416 +}