diff -r 000000000000 -r c24a6fa957db core/MenuVoting.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/MenuVoting.cpp Sun Jul 06 04:49:55 2008 +0000 @@ -0,0 +1,491 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include +#include "MenuVoting.h" +#include "PlayerManager.h" +#include "sourcemm_api.h" + +float g_next_vote = 0.0f; + +#if defined ORANGEBOX_BUILD +void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue); +#else +void OnVoteDelayChange(ConVar *cvar, const char *value); +#endif +ConVar sm_vote_delay("sm_vote_delay", + "30", + 0, + "Sets the recommended time in between public votes", + false, + 0.0, + false, + 0.0, + OnVoteDelayChange); + +#if defined ORANGEBOX_BUILD +void OnVoteDelayChange(IConVar *cvar, const char *value, float flOldValue) +#else +void OnVoteDelayChange(ConVar *cvar, const char *value) +#endif +{ + /* See if the new vote delay isn't something we need to account for */ + if (sm_vote_delay.GetFloat() < 1.0f) + { + g_next_vote = 0.0f; + return; + } + + /* If there was never a last vote, ignore this change */ + if (g_next_vote < 0.1f) + { + return; + } + + /* Subtract the original value, then add the new one. */ + g_next_vote -= (float)atof(value); + g_next_vote += sm_vote_delay.GetFloat(); +} + +unsigned int VoteMenuHandler::GetRemainingVoteDelay() +{ + if (g_next_vote <= gpGlobals->curtime) + { + return 0; + } + + return (unsigned int)(g_next_vote - gpGlobals->curtime); +} + +void VoteMenuHandler::OnSourceModAllInitialized() +{ + g_Players.AddClientListener(this); +} + +void VoteMenuHandler::OnSourceModShutdown() +{ + g_Players.RemoveClientListener(this); +} + +void VoteMenuHandler::OnSourceModLevelChange(const char *mapName) +{ + g_next_vote = 0.0f; +} + +unsigned int VoteMenuHandler::GetMenuAPIVersion2() +{ + return m_pHandler->GetMenuAPIVersion2(); +} + +void VoteMenuHandler::OnClientDisconnected(int client) +{ + if (!IsVoteInProgress()) + { + return; + } + + /* Wipe out their vote if they had one. We have to make sure the the the + * newly connected client is not allowed to vote. + */ + int item; + if ((item = m_ClientVotes[client]) >= -1) + { + if (item >= 0) + { + assert((unsigned)item < m_Items); + assert(m_Votes[item] > 0); + m_Votes[item]--; + } + m_ClientVotes[client] = -2; + } +} + +bool VoteMenuHandler::IsVoteInProgress() +{ + return (m_pCurMenu != NULL); +} + +bool VoteMenuHandler::StartVote(IBaseMenu *menu, + unsigned int num_clients, + int clients[], + unsigned int max_time, + unsigned int flags/* =0 */) +{ + if (!InitializeVoting(menu, menu->GetHandler(), max_time, flags)) + { + return false; + } + + /* Note: we can use game time and not universal time because + * if we're voting then players are in-game. + */ + + float fVoteDelay = sm_vote_delay.GetFloat(); + if (fVoteDelay < 1.0) + { + g_next_vote = 0.0; + } + else + { + /* This little trick breaks for infinite votes! + * However, we just ignore that since those 1) shouldn't exist and + * 2) people must be checking IsVoteInProgress() beforehand anyway. + */ + g_next_vote = gpGlobals->curtime + fVoteDelay + (float)max_time; + } + + m_fStartTime = gpGlobals->curtime; + m_nMenuTime = max_time; + + for (unsigned int i=0; i 256) + { + continue; + } + menu->Display(clients[i], max_time, this); + } + + StartVoting(); + + return true; +} + +bool VoteMenuHandler::IsClientInVotePool(int client) +{ + if (client < 1 + || client > g_Players.MaxClients() + || m_pCurMenu == NULL) + { + return false; + } + + return (m_ClientVotes[client] > -2); +} + +bool VoteMenuHandler::GetClientVoteChoice(int client, unsigned int *pItem) +{ + if (!IsClientInVotePool(client) + || m_ClientVotes[client] == -1) + { + return false; + } + + *pItem = m_ClientVotes[client]; + + return true; +} + +bool VoteMenuHandler::RedrawToClient(int client) +{ + unsigned int time_limit; + + if (!IsClientInVotePool(client)) + { + return false; + } + + if (m_nMenuTime == MENU_TIME_FOREVER) + { + time_limit = m_nMenuTime; + } + else + { + time_limit = (int)((float)m_nMenuTime - (gpGlobals->curtime - m_fStartTime)); + + /* Make sure this doesn't round to zero */ + if (time_limit == MENU_TIME_FOREVER) + { + time_limit = 1; + } + } + + return m_pCurMenu->Display(client, time_limit, this); +} + +bool VoteMenuHandler::InitializeVoting(IBaseMenu *menu, + IMenuHandler *handler, + unsigned int time, + unsigned int flags) +{ + if (IsVoteInProgress()) + { + return false; + } + + InternalReset(); + + /* Mark all clients as not voting */ + for (int i=1; i<=gpGlobals->maxClients; i++) + { + m_ClientVotes[i] = -2; + } + + m_Items = menu->GetItemCount(); + + if (m_Votes.size() < (size_t)m_Items) + { + /* Only clear the items we need to... */ + size_t size = m_Votes.size(); + for (size_t i=0; iOnMenuStart(m_pCurMenu); + + return true; +} + +void VoteMenuHandler::StartVoting() +{ + if (!m_pCurMenu) + { + return; + } + + m_bStarted = true; + + m_pHandler->OnMenuVoteStart(m_pCurMenu); + + /* By now we know how many clients were set. + * If there are none, we should end IMMEDIATELY. + */ + if (m_Clients == 0) + { + EndVoting(); + } +} + +void VoteMenuHandler::DecrementPlayerCount() +{ + assert(m_Clients > 0); + + m_Clients--; + + if (m_bStarted && m_Clients == 0) + { + EndVoting(); + } +} + +int SortVoteItems(const void *item1, const void *item2) +{ + return ((menu_vote_result_t::menu_item_vote_t *)item2)->count + - ((menu_vote_result_t::menu_item_vote_t *)item1)->count; +} + +void VoteMenuHandler::EndVoting() +{ + /* Set when the next delay ends. We ignore cancellation because a menu + * was, at one point, displayed, which is all that counts. However, we + * do re-calculate the time just in case the menu had no time limit. + */ + float fVoteDelay = sm_vote_delay.GetFloat(); + if (fVoteDelay < 1.0) + { + g_next_vote = 0.0; + } + else + { + g_next_vote = gpGlobals->curtime + fVoteDelay; + } + + if (m_bCancelled) + { + /* If we were cancelled, don't bother tabulating anything. + * Reset just in case someone tries to redraw, which means + * we need to save our states. + */ + IBaseMenu *menu = m_pCurMenu; + IMenuHandler *handler = m_pHandler; + InternalReset(); + handler->OnMenuVoteCancel(menu, VoteCancel_Generic); + handler->OnMenuEnd(menu, MenuEnd_VotingCancelled); + return; + } + + menu_vote_result_t vote; + menu_vote_result_t::menu_client_vote_t client_vote[256]; + menu_vote_result_t::menu_item_vote_t item_vote[256]; + + memset(&vote, 0, sizeof(vote)); + + /* Build the item list */ + for (unsigned int i=0; i 0) + { + item_vote[vote.num_items].count = m_Votes[i]; + item_vote[vote.num_items].item = i; + vote.num_votes += m_Votes[i]; + vote.num_items++; + } + } + vote.item_list = item_vote; + + if (!vote.num_votes) + { + IBaseMenu *menu = m_pCurMenu; + IMenuHandler *handler = m_pHandler; + InternalReset(); + handler->OnMenuVoteCancel(menu, VoteCancel_NoVotes); + handler->OnMenuEnd(menu, MenuEnd_VotingCancelled); + return; + } + + /* Build the client list */ + for (int i=1; i<=gpGlobals->maxClients; i++) + { + if (m_ClientVotes[i] >= -1) + { + client_vote[vote.num_clients].client = i; + client_vote[vote.num_clients].item = m_ClientVotes[i]; + vote.num_clients++; + } + } + vote.client_list = client_vote; + + /* Sort the item list descending like we promised */ + qsort(item_vote, + vote.num_items, + sizeof(menu_vote_result_t::menu_item_vote_t), + SortVoteItems); + + /* Save states, then clear what we've saved. + * This makes us re-entrant, which is always the safe way to go. + */ + IBaseMenu *menu = m_pCurMenu; + IMenuHandler *handler = m_pHandler; + InternalReset(); + + /* Send vote info */ + handler->OnMenuVoteResults(menu, &vote); + handler->OnMenuEnd(menu, MenuEnd_VotingDone); +} + +void VoteMenuHandler::OnMenuStart(IBaseMenu *menu) +{ + m_Clients++; +} + +void VoteMenuHandler::OnMenuEnd(IBaseMenu *menu, MenuEndReason reason) +{ + DecrementPlayerCount(); +} + +void VoteMenuHandler::OnMenuCancel(IBaseMenu *menu, int client, MenuCancelReason reason) +{ + m_pHandler->OnMenuCancel(menu, client, reason); +} + +void VoteMenuHandler::OnMenuDisplay(IBaseMenu *menu, int client, IMenuPanel *display) +{ + m_ClientVotes[client] = -1; + m_pHandler->OnMenuDisplay(menu, client, display); +} + +unsigned int VoteMenuHandler::OnMenuDisplayItem(IBaseMenu *menu, int client, IMenuPanel *panel, unsigned int item, const ItemDrawInfo &dr) +{ + return m_pHandler->OnMenuDisplayItem(menu, client, panel, item, dr); +} + +void VoteMenuHandler::OnMenuDrawItem(IBaseMenu *menu, int client, unsigned int item, unsigned int &style) +{ + m_pHandler->OnMenuDrawItem(menu, client, item, style); +} + +void VoteMenuHandler::OnMenuSelect(IBaseMenu *menu, int client, unsigned int item) +{ + /* Check by our item count, NOT the vote array size */ + if (item < m_Items) + { + m_ClientVotes[client] = item; + m_Votes[item]++; + m_NumVotes++; + } + + m_pHandler->OnMenuSelect(menu, client, item); +} + +void VoteMenuHandler::OnMenuSelect2(IBaseMenu *menu, int client, unsigned int item, unsigned int item_on_page) +{ + if (m_pHandler->GetMenuAPIVersion2() >= 13) + { + m_pHandler->OnMenuSelect2(menu, client, item, item_on_page); + } +} + +void VoteMenuHandler::InternalReset() +{ + m_Clients = 0; + m_Items = 0; + m_bStarted = false; + m_pCurMenu = NULL; + m_NumVotes = 0; + m_bCancelled = false; + m_pHandler = NULL; +} + +void VoteMenuHandler::CancelVoting() +{ + if (m_bCancelled || !m_pCurMenu) + { + return; + } + m_bCancelled = true; + m_pCurMenu->Cancel(); +} + +IBaseMenu *VoteMenuHandler::GetCurrentMenu() +{ + return m_pCurMenu; +} + +bool VoteMenuHandler::IsCancelling() +{ + return m_bCancelled; +}