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